Обобщённые функции
Часть раздела Объектно-ориентированное программирование путешествия по Rust на Coddy. Урок 27 из 61.
Обобщения не ограничиваются структурами. Ты также можешь писать отдельные функции, которые работают с любым типом. Это полезно, когда нужна вспомогательная функция, не принадлежащая к конкретной структуре, но при этом остающаяся гибкой.
Синтаксис повторяет то, что вы уже видели в структурах. Объявите обобщённый параметр в угловых скобках после имени функции, а затем используйте его в параметрах и типе возвращаемого значения:
fn identity<T>(value: T) -> T {
value
}
Эта функция identity принимает значение любого типа и возвращает его без изменений. Запись <T> объявляет обобщённый тип, 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)
Обобщённые функции позволяют один раз написать повторно используемую логику и применять её к разным типам, сокращая дублирование кода и сохраняя типобезопасность.
Задание
ЛегкоДавай создадим модуль утилит с обобщёнными функциями, которые могут работать с любым типом! Ты создашь отдельные функции, демонстрирующие, как обобщения делают код гибким и повторно используемым, не привязывая его к определённой структуре.
Ты организуешь код в двух файлах:
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 обрабатывает два совершенно разных типа: в этом и заключается гибкость обобщённых функций!
Ты получишь четыре значения: целое число, строку, ещё одно целое число и ещё одну строку.
Попробуйте сами
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})
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Методы и поведение
Введение в блоки реализацииПараметр SelfИзменяемые методыАссоциированные функцииНесколько блоков реализацииЦепочки вызовов методовИтоги: действия с Rectangle4Проект: Виртуальный питомец
Определение питомцаКормление питомца2Инкапсуляция и модули
Основы модулейКлючевое слово PublicПриватные поляГеттерыСеттерыИтоги — Безопасный сейф5Обобщения
Обобщённые структурыОбобщённые методыНесколько обобщённых типовОбобщённые функцииПовторение — Координатная точка8Трейты как ограничения
Синтаксис ограничений трейтовМножественные ограниченияКонструкция whereВозврат типов с трейтамиИтоги — Обобщённый принтер11Паттерны проектирования в Rust
Паттерн NewtypeКомпозицияТрейт DropFrom и IntoИтоги — Мок-объект Smart Pointer3Продвинутые перечисления
Перечисления с даннымиМетоды в перечисленияхСопоставление вариантов с даннымиСнова об Option EnumИтоги — перечисление Shape6Определение трейтов
Что такое трейт?Реализация трейтовРеализации по умолчаниюПереопределение реализаций по умолчаниюТрейты с параметрамиИтоги — Media PlayerПотренируйтесь самостоятельно: Онлайн-компилятор Rust