Обобщённые функции
Часть раздела Объектно-ориентированное программирование путешествия по Rust на Coddy. Урок 27 из 61.
Обобщённые типы не ограничиваются структурами. Вы также можете писать отдельные функции, которые работают с любым типом. Это полезно, когда вам нужна вспомогательная функция, не относящаяся к определённой структуре, но при этом остающаяся гибкой.
Синтаксис похож на то, что вы уже видели в структурах. Объявите generic-параметр в угловых скобках после имени function, затем используйте его в parameters и возвращаемом типе:
fn identity<T>(value: T) -> T {
value
}
Эта функция identity принимает значение любого типа и возвращает его без изменений. <T> объявляет generic, value: T принимает его в качестве параметра, а -> T указывает возвращаемый тип. Когда вы вызываете function, Rust выводит конкретный тип:
let num = identity(42); // T — это i32
let text = identity("hello"); // T — это &str
Вы также можете использовать несколько generic параметров в функциях, как и в структурах:
fn make_pair<T, U>(first: T, second: U) -> (T, U) {
(first, second)
}
let pair = make_pair(10, "ten"); // возвращает (i32, &str)
Обобщённые функции let вам один раз записать повторно используемую логику и применять её к многим типам, уменьшая дублирование кода и сохраняя безопасность типов.
Задание
ЛегкоДавайте создадим utility-модуль с generic function, которые могут работать с любым типом! Вы создадите самостоятельные function, демонстрирующие, как generics делают ваш код гибким и повторно используемым, не привязывая его к определённой структуре.
Вы организуете код в двух файлах:
utils.rs: создайте набор общедоступных generic utility function:wrap_in_pair<T>: принимает single значение и returns tuple, containing это значение дважды:(value, value). Для этого требуется, чтобы тип поддерживалClone, поэтому используйте<T: Clone>swap<T, U>: принимает два значения потенциально разных типов и returns их в reversed order в виде tuple(U, T)
main.rs: импортируйте utility-модуль и продемонстрируйте работу этих generic function с разными типами. Покажите, как одни и те же определения function без проблем обрабатывают integer, числа с плавающей точкой и strings.
В основном файле продемонстрируйте свои utility function следующим образом:
- Используйте
wrap_in_pairс integer (first input, разобранным какi32) и выведите оба элемента - Используйте
wrap_in_pairсо string (second input) и выведите оба элемента - Используйте
swapс integer (third input, разобранным какi32) и string (fourth input), затем выведите swapped result
Ваш вывод должен соответствовать этому формату:
Pair of ints: ({value}, {value})
Pair of strings: ({value}, {value})
Swapped: ({string}, {int})Например, для inputs 5, hello, 42 и world:
Pair of ints: (5, 5)
Pair of strings: (hello, hello)
Swapped: (world, 42)Обратите внимание, как wrap_in_pair работает одинаково для integer и strings, а swap обрабатывает два полностью разных типа: в этом и заключается гибкость generic function!
Вы получите four inputs: integer, string, ещё один integer и ещё один string.
Попробуйте сами
mod utils;
use std::io;
fn main() {
// Считать четыре ввода
let mut input1 = String::new();
io::stdin().read_line(&mut input1).expect("Failed to read line");
let num1: i32 = input1.trim().parse().expect("Invalid integer");
let mut input2 = String::new();
io::stdin().read_line(&mut input2).expect("Failed to read line");
let str1 = input2.trim().to_string();
let mut input3 = String::new();
io::stdin().read_line(&mut input3).expect("Failed to read line");
let num2: i32 = input3.trim().parse().expect("Invalid integer");
let mut input4 = String::new();
io::stdin().read_line(&mut input4).expect("Failed to read line");
let str2 = input4.trim().to_string();
// TODO: Используйте wrap_in_pair с num1 и выведите результат
// Format: Pair of ints: ({value}, {value})
// TODO: Используйте wrap_in_pair с str1 и выведите результат
// Format: Pair of strings: ({value}, {value})
// TODO: Используйте swap с num2 и str2, затем выведите результат обмена
// Формат: Обменяно: ({string}, {int})
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Методы и поведение
Введение в блоки реализацииПараметр SelfИзменяемые методыАссоциированные функцииНесколько блоков реализацииЦепочки вызовов методовИтоги: действия с Rectangle4Проект: Виртуальный питомец
Определение питомцаКормление питомца2Инкапсуляция и модули
Основы модулейКлючевое слово PublicПриватные поляГеттерыСеттерыИтоги — Безопасный сейф5Обобщения
Обобщённые структурыОбобщённые методыНесколько обобщённых типовОбобщённые функцииПовторение — Координатная точка8Трейты как ограничения
Синтаксис ограничений трейтовМножественные ограниченияКонструкция whereВозврат типов с трейтамиИтоги — Обобщённый принтер11Паттерны проектирования в Rust
Паттерн NewtypeКомпозицияТрейт DropFrom и IntoИтоги — Мок-объект Smart Pointer3Продвинутые перечисления
Перечисления с даннымиМетоды в перечисленияхСопоставление вариантов с даннымиСнова об Option EnumИтоги — перечисление Shape6Определение трейтов
Что такое трейт?Реализация трейтовРеализации по умолчаниюПереопределение реализаций по умолчаниюТрейты с параметрамиИтоги — Media PlayerПотренируйтесь самостоятельно: Онлайн-компилятор Rust