Введение
Конкретные типы удобны, когда данные всегда имеют одну фиксированную форму. Однако повторение почти одинаковых типов для чисел и текста усложняет сопровождение программы. Вы уже использовали стандартные обобщённые типы, такие как Option<u32>, Vec<String>, HashMap<String, u32> и Result<T, E>. В этой лабораторной работе вы самостоятельно определите обобщённый тип. Обобщения Rust позволяют одной декларации работать с несколькими конкретными типами, при этом компилятор по-прежнему проверяет каждое использование.
В этой лабораторной работе вы сделаете подготовленную структуру Pair обобщённой, а затем обобщите небольшую вспомогательную функцию, работающую с заимствованными данными. Более длинная программа и примеры данных уже подготовлены, поэтому вы сосредоточитесь только на нескольких позициях типов, которые обеспечивают повторное использование.
Сделайте структуру Pair обобщённой
На этом шаге вы замените фиксированный тип i32 в структуре одним параметром типа, чтобы одно и то же определение могло хранить оценки или названия маршрутов.
Перейдите в подготовленный пакет Cargo:
cd /home/labex/project/pair-demo
Просмотрите короткий исходный файл. Команда sed -n выводит указанный диапазон строк, не изменяя файл:
sed -n '1,120p' src/main.rs
Значение scores содержит целые числа, а routes — принадлежащие программе значения типа String. Текущее определение Pair задаёт для обоих полей тип i32, поэтому структура не может представлять оба значения. Подготовленный атрибут #[derive(Debug)] добавил бы к типу форматирование для отладки, но видимая программа это форматирование не использует; оно не относится к задаче об обобщениях.
Обобщённый тип вводит заполнитель, например T, внутри угловых скобок. Каждый созданный объект Pair всё равно имеет один конкретный тип: компилятор выводит Pair<i32> для scores и Pair<String> для routes.
Откройте исходный файл в Nano:
nano src/main.rs
Внесите в верхней части файла следующие три небольших изменения:
- Замените
struct Pair {наstruct Pair<T> {. - Замените тип
leftсi32наT. - Замените тип
rightсi32наT.
Итоговое определение структуры должно выглядеть так:
struct Pair<T> {
left: T,
right: T,
}
Здесь оба поля используют один и тот же T, поэтому одна пара не сможет случайно содержать поля несовместимых типов. Сохраните файл с помощью Ctrl+O, нажмите Enter, затем выйдите с помощью Ctrl+X.
Попросите Cargo проверить типы в пакете:
cargo check
Строка, заканчивающаяся на Finished, означает, что компилятор принял и Pair<i32>, и Pair<String>. Теперь запустите программу:
cargo run --quiet
scores: 82 and 91
routes: north and east
first score: 82
Первые две строки подтверждают, что одно определение структуры теперь хранит два разных конкретных типа. Существующая вспомогательная функция first по-прежнему принимает только Pair<i32>. На следующем шаге вы обобщите её.
Обобщите вспомогательную функцию с заимствованием
На этом шаге вы добавите функции first собственный параметр типа, чтобы она могла заимствовать любой Pair<T> и возвращать ссылку на его первое значение.
Просмотрите текущую вспомогательную функцию в верхней части файла:
sed -n '1,30p' src/main.rs
Её входной тип Pair<i32> и выходной тип &i32 делают функцию пригодной только для оценок. Обобщённая функция объявляет <T> после своего имени, а затем использует тот же T во всех связанных входных и выходных типах.
Откройте файл:
nano src/main.rs
Измените только объявление функции:
fn first(pair: &Pair<i32>) -> &i32 {
заменив его на:
fn first<T>(pair: &Pair<T>) -> &T {
Функция заимствует пару и возвращает заимствованное поле, поэтому она не перемещает и не копирует сохранённое значение. Rust может вывести T из каждого аргумента, поэтому вызывающему коду не нужно явно указывать тип.
Затем замените // FIRST_ROUTE_REPORT одним вызовом, который подтверждает, что вспомогательная функция также работает с текстом:
println!("first route: {}", first(&routes));
Сохраните файл и выйдите из Nano. Затем проверьте типы и запустите готовую программу:
cargo check
cargo run --quiet
scores: 82 and 91
routes: north and east
first score: 82
first route: north
Две строки first получены с помощью одной и той же обобщённой функции. Для вызова с оценкой Rust выводит T = i32, а для вызова с маршрутом — T = String, после чего проверяет каждый вызов с соответствующим конкретным типом. Повторное использование не отменяет статическую проверку типов.
Итоги
Вы заменили одну конкретную пару на Pair<T>, использовали её и с целыми числами, и со строками, а также обобщили вспомогательную функцию с заимствованием, не добавляя трейты и не дублируя реализации.


