Обобщённые функции
Часть раздела Object Oriented Programming путешествия по Rust на Coddy — урок 27 из 61.
Обобщения не ограничиваются структурами — вы также можете писать отдельные функции, которые работают с любым типом. Это полезно, когда вам нужна вспомогательная функция, которая не принадлежит конкретной структуре, но при этом должна оставаться гибкой.
Синтаксис повторяет то, что вы уже видели в структурах. Объявите универсальный параметр в угловых скобках после имени функции, а затем используйте его в параметрах и типе возвращаемого значения:
fn identity<T>(value: T) -> T {
value
}
Эта функция identity принимает значение любого типа и возвращает его без изменений. <T> объявляет универсальный параметр (generic), value: T принимает его в качестве параметра, а -> T указывает тип возвращаемого значения. При вызове функции Rust выводит конкретный тип:
let num = identity(42); // T — это i32
let text = identity("hello"); // T — это &str
Вы также можете использовать несколько универсальных параметров в функциях, точно так же, как и со структурами:
fn make_pair<T, U>(first: T, second: U) -> (T, U) {
(first, second)
}
let pair = make_pair(10, "ten"); // возвращает (i32, &str)
Обобщенные функции позволяют написать переиспользуемую логику один раз и применять её ко многим типам, сокращая дублирование кода и сохраняя при этом типобезопасность.
Задание
ЛегкоДавайте создадим модуль утилит с универсальными (generic) функциями, которые могут работать с любым типом! Вы создадите отдельные функции, которые продемонстрируют, как обобщения делают ваш код гибким и переиспользуемым, не привязываясь к конкретной структуре.
Вы организуете свой код в двух файлах:
utils.rs: Создайте коллекцию публичных универсальных функций-утилит:wrap_in_pair<T>— принимает одно значение и возвращает кортеж, содержащий это значение дважды:(value, value). Это требует, чтобы тип реализовывал трейтClone, поэтому используйте<T: Clone>swap<T, U>— принимает два значения потенциально разных типов и возвращает их в обратном порядке в виде кортежа(U, T)
main.rs: Импортируйте ваш модуль утилит и продемонстрируйте работу этих универсальных функций с разными типами. Покажите, как одни и те же определения функций беспрепятственно обрабатывают целые числа, числа с плавающей запятой и строки.
В основном файле продемонстрируйте работу ваших функций-утилит следующим образом:
- Используйте
wrap_in_pairс целым числом (первый ввод, преобразованный вi32) и выведите оба элемента - Используйте
wrap_in_pairсо строкой (второй ввод) и выведите оба элемента - Используйте
swapс целым числом (третий ввод, преобразованный вi32) и строкой (четвертый ввод), затем выведите результат перестановки
Ваш вывод должен соответствовать следующему формату:
Pair of ints: ({value}, {value})
Pair of strings: ({value}, {value})
Swapped: ({string}, {int})Например, при входных данных 5, hello, 42 и world:
Pair of ints: (5, 5)
Pair of strings: (hello, hello)
Swapped: (world, 42)Обратите внимание, как wrap_in_pair одинаково работает как для целых чисел, так и для строк, а swap обрабатывает два совершенно разных типа — в этом и заключается гибкость универсальных функций!
Вы получите четыре входных значения: целое число, строку, еще одно целое число и еще одну строку.
Шпаргалка
Обобщенные функции позволяют писать автономные вспомогательные функции, которые работают с любым типом. Объявите обобщенный параметр в угловых скобках после имени функции:
fn identity<T>(value: T) -> T {
value
}
Синтаксис <T> объявляет параметр обобщенного типа, который затем можно использовать в параметрах функции и возвращаемых типах. Rust выводит конкретный тип при вызове функции:
let num = identity(42); // T is i32
let text = identity("hello"); // T is &str
В одной функции можно использовать несколько обобщенных параметров:
fn make_pair<T, U>(first: T, second: U) -> (T, U) {
(first, second)
}
let pair = make_pair(10, "ten"); // returns (i32, &str)
Когда обобщенному типу требуются определенные возможности, используйте ограничения типажа (trait bounds):
fn duplicate<T: Clone>(value: T) -> (T, T) {
(value.clone(), value)
}
Попробуйте сами
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 и выведите результат
// Формат: Pair of ints: ({value}, {value})
// TODO: Используйте wrap_in_pair с str1 и выведите результат
// Формат: Pair of strings: ({value}, {value})
// TODO: Используйте swap с num2 и str2, затем выведите результат обмена
// Формат: Swapped: ({string}, {int})
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Object Oriented Programming
1Методы и поведение
Введение в блоки реализацииПараметр SelfИзменяемые методыАссоциированные функцииНесколько блоков реализацииЦепочки вызовов методовИтоги: действия с Rectangle4Проект: Виртуальный питомец
Определение питомцаКормление питомца2Инкапсуляция и модули
Основы модулейКлючевое слово PublicПриватные поляГеттерыСеттерыИтоги — Безопасный сейф5Обобщения
Обобщённые структурыОбобщённые методыНесколько обобщённых типовОбобщённые функцииПовторение — Координатная точка8Трейты как ограничения
Синтаксис ограничений трейтовМножественные ограниченияКонструкция whereВозврат типов с трейтамиИтоги — Обобщённый принтер11Паттерны проектирования в Rust
Паттерн NewtypeКомпозицияТрейт DropFrom и IntoИтоги — Мок-объект Smart Pointer3Продвинутые перечисления
Перечисления с даннымиМетоды в перечисленияхСопоставление вариантов с даннымиСнова об Option EnumИтоги — перечисление Shape6Определение трейтов
Что такое трейт?Реализация трейтовРеализации по умолчаниюПереопределение реализаций по умолчаниюТрейты с параметрамиИтоги — Media Player