Обобщения (generics)
Дженерики позволяют писать функции и структуры, работающие с любым типом, без потери типобезопасности.
Обобщения (generics) — параметры-типы, которые подставляются при использовании, позволяя писать один код для многих типов.
Зачем нужны дженерики
Допустим, нужна функция, которая находит максимум в срезе. Без дженериков пришлось бы писать отдельную версию для i32, для f64, для char — копипаст с разными типами. Дженерики дают один код, работающий для всех типов сразу, и при этом полностью типобезопасный.
Обобщённая функция
Параметр-тип записывают в угловых скобках после имени: fn name<T>(...). Внутри T ведёт себя как обычный тип.
// T — любой тип; пара значений просто меняется местами
fn swap<T>(a: T, b: T) -> (T, T) {
(b, a)
}
fn main() {
let (x, y) = swap(1, 2);
let (s, t) = swap("раз", "два");
println!("{x} {y}");
println!("{s} {t}");
}Вывод:
2 1 два раз
Ограничения типов (trait bounds)
Иногда тип T должен что-то уметь. Чтобы найти максимум, элементы надо уметь сравнивать. Это выражают ограничением: T: PartialOrd означает «T должен поддерживать сравнение». Подробнее о трейтах — в следующем уроке, пока важно увидеть синтаксис.
// T должен поддерживать сравнение (PartialOrd) и копирование (Copy)
fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
let mut max = list[0];
for &item in list {
if item > max {
max = item;
}
}
max
}
fn main() {
let nums = [3, 7, 2, 9, 4];
let chars = ['a', 'z', 'm'];
println!("{}", largest(&nums));
println!("{}", largest(&chars));
}Вывод:
9 z
Если бы мы передали тип, который нельзя сравнивать, код не скомпилировался бы — ограничение проверяется заранее.
Обобщённые структуры
Структуры тоже бывают обобщёнными. Например, «точка» с координатами любого числового типа.
struct Point<T> {
x: T,
y: T,
}
impl<T: std::fmt::Display> Point<T> {
fn show(&self) {
println!("({}, {})", self.x, self.y);
}
}
fn main() {
let int_point = Point { x: 1, y: 2 };
let float_point = Point { x: 1.5, y: 2.5 };
int_point.show();
float_point.show();
}Вывод:
(1, 2) (1.5, 2.5)
Цена дженериков — ноль
Важная деталь: дженерики в Rust бесплатны в рантайме. На этапе компиляции происходит мономорфизация — для каждого реально использованного типа генерируется своя специализированная версия кода. Результат такой же быстрый, как если бы вы написали версии вручную, но без копипаста.
Итог
- Дженерики (
<T>) дают один код для многих типов без потери типобезопасности. - Ограничения вида
T: PartialOrdтребуют, чтобы тип поддерживал нужные возможности. - Мономорфизация делает дженерики бесплатными в рантайме — это абстракция нулевой стоимости.