Шаблонные типы данных, типажи и время жизни

Наверное, каждый прагматичный язык программирования имеет в своём арсенале эффективное средство. борьбы с дублированием кода. В Rust одним из таким средств является концепция шаблонных типов данных - generics. Это абстрактные типы для конкретных типов или свойств. Когда мы пишем и компилируем код мы можем подставить конкретный тип данных. Мы можем решать задачи с различными типами данных без учёта их специфики.

Эта концепция напоминает работу функции, тип входных параметров которой заранее не известен. Мы также можем создавать функции, которые получают шаблонный тип данных в качестве параментра. Мы уже использовали такие типы данных в Главе 6 Option<T>, в Главе 8 Vec<T> и HashMap<K, V>, в Главе 9 Result<T, E>. В этой главе мы рассмотрим, как определить наши собственные типы данных, функции и методы используя возможности шаблонных типов данных.

Прежде всего, мы рассмотрим механизм получения функции, которая уменьшает дублирование кода. Далее, мы будем использовать тот же механизм для получения функции с шаблонным типом данных. Мы также можем использовать шаблонные типы данных при определении структур и перечислений.

После этого, мы поговорим о типажах. Типажи - это способ определения поведения в стиле шаблонных типов данных. Типажи могут быть комбинацией шаблонных типов данных для уменьшения возможных типов данных, которые имеют определённое поведение.

В конец главы, мы поговорим о времени жизни. Это функционал похож на шаблонные типы данных, которое даёт нам информацию из компилятора о связях между ссылками в памяти. Время жизни - это опция в Rust, которая позволяет заимствовать в различных ситуациях и в тоже время помогает компилятору проверять ссылочную целостность.

Удаление дублирование кода с помощью выделение функции

Прежде чем перейти к рассмотрению синтаксиса шаблонных типов данных, предлагаю рассмотреть технику устранения дублирования кода. Поняв механизм устранения дублирования применим его для создания шаблонных типов данных.

Рассмотрим небольшую программу, которая ищет наибольшее число в списке (Текст кода 10-1):

Filename: src/main.rs

fn main() {
    let number_list = vec![34, 50, 25, 100, 65];

    let mut largest = number_list[0];

    for number in number_list {
        if number > largest {
            largest = number;
        }
    }

    println!("The largest number is {}", largest);
#  assert_eq!(largest, 100);
}

Текст кода 10-1: Программа поиска наибольшего числа в списке

В программе вектор целых чисел сохраняется в переменной number_list. Первое значение из списка получает переменная largest. Далее, итератор перебирает элементы вектора. Если текущий элемент больше наибольшего, его значение присваивается наибольшему. После перебора всех элементов, переменная largest хранит наибольшее числовое значение.

Если необходимо искать наибольшее число в друх различных списках, мы должны будем дублировать код и использовать такую же логику в двух различных местах программы. Текст кода 10-2:

Filename: src/main.rs

fn main() {
    let number_list = vec![34, 50, 25, 100, 65];

    let mut largest = number_list[0];

    for number in number_list {
        if number > largest {
            largest = number;
        }
    }

    println!("The largest number is {}", largest);

    let number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8];

    let mut largest = number_list[0];

    for number in number_list {
        if number > largest {
            largest = number;
        }
    }

    println!("The largest number is {}", largest);
}

Текст кода 10-2: Программа поиска наибольшего числа в двух списках

Несмотря на то, что код программы работает - этот код может содержать ошибки. В этом код можно улучшить (прежде всего логику его работы).

Для устранения дублирования нам надо создать абстрактный код. В данном случае, это это будет абстрактная функция, которая принимает любые списки целочисленных данных. Создание и использование такой функции увеличит ясность и уменьшит количество кода. В программе 10-3, мы выбрали код, который ищет наибольшее значение в нескольких списках:

Filename: src/main.rs

fn largest(list: &[i32]) -> i32 {
    let mut largest = list[0];

    for &item in list.iter() {
        if item > largest {
            largest = item;
        }
    }

    largest
}

fn main() {
    let number_list = vec![34, 50, 25, 100, 65];

    let result = largest(&number_list);
    println!("The largest number is {}", result);
#    assert_eq!(result, 100);

    let number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8];

    let result = largest(&number_list);
    println!("The largest number is {}", result);
#    assert_eq!(result, 6000);
}

Текст программа 10-3: Функция поиска наибольшего элемента в списках числовых значений

Функция принимает параметр list, который представляет собой любой отрезок числовых данных. Когда функция вызывается, функция работает с входными данными.

Порядок устранения дублирования кода:

  1. Находим дублированный код.
  2. Выбираем дублирующий код из тела функции. Создаем отдельную функцию с этим кодом.
  3. Заменяем код на вызов функции.

Этот же алгоритм можно применить для создания шаблонных решений. Также код функции будет обрабатывать какой-то абстрактный список данных. У шаблонных решений есть свои синтаксические отличия, о которых мы поговорим далее.

Что будет если у нас будет две функции, одна ищет наибольшее значение в отрезке с типами данных i32, а вторая с char? Как в этом случае избежать дублирования. Об этом поговорим на следующей главе.