Обобщённое программирование
Иногда, при написании функции или типа данных, мы можем захотеть, чтобы они работали для нескольких типов аргументов. К счастью, у Rust есть возможность, которая даёт нам лучший способ реализовать это: обобщённое программирование. Обобщённое программирование называется «параметрическим полиморфизмом» в теории типов. Это означает, что типы или функции имеют несколько форм (poly — кратно, morph — форма) по данному параметру («параметрический»).
В любом случае, хватит о теории типов; давайте рассмотрим какой-нибудь
обобщённый код. Стандартная библиотека Rust предоставляет тип Option<T>
,
который является обобщённым типом:
enum Option<T> {
Some(T),
None,
}
Часть <T>
, которую вы раньше уже видели несколько раз, указывает, что это
обобщённый тип данных. Внутри перечисления, везде, где мы видим T
, мы
подставляем вместо этого абстрактного типа тот, который используется в
обобщении. Вот пример использования Option<T>
с некоторыми дополнительными
аннотациями типов:
let x: Option<i32> = Some(5);
В определении типа мы используем Option<i32>
. Обратите внимание, что это очень
похоже на Option<T>
. С той лишь разницей, что, в данном конкретном Option
,
T
имеет значение i32
. В правой стороне выражения мы используем Some(T)
,
где T
равно 5
. Так как 5
является представителем типа i32
, то типы по
обе стороны совпадают, поэтому компилятор счастлив. Если же они не совпадают, то
мы получим ошибку:
let x: Option<f64> = Some(5);
// error: mismatched types: expected `core::option::Option<f64>`,
// found `core::option::Option<_>` (expected f64 but found integral variable)
Но это не значит, что мы не можем сделать Option<T>
, который содержит f64
!
Просто типы должны совпадать:
let x: Option<i32> = Some(5);
let y: Option<f64> = Some(5.0f64);
Это просто прекрасно. Одно определение — многостороннее использование.
Обобщать можно более, чем по одному параметру. Рассмотрим другой обобщённый тип
из стандартной библиотеки Rust — Result<T, E>
:
enum Result<T, E> {
Ok(T),
Err(E),
}
Этот тип является обобщённым сразу для двух типов: T
и E
. Кстати,
заглавные буквы могут быть любыми. Мы могли бы определить Result<T, E>
как:
enum Result<A, Z> {
Ok(A),
Err(Z),
}
если бы захотели. Соглашение гласит, что первый обобщённый параметр для 'типа'
должен быть T
, и что для 'ошибки' используется E
. Но Rust не проверяет
этого.
Тип Result<T, E>
предназначен для того, чтобы возвращать результат вычисления,
и имеет возможность вернуть ошибку, если произойдёт какой-либо сбой.
Обобщённые функции
Мы можем задавать функции, которые принимают обобщённые типы, с помощью аналогичного синтаксиса:
fn takes_anything<T>(x: T) {
// делаем что-то с x
}
Синтаксис состоит из двух частей: <T>
говорит о том, что «эта функция является
обобщённой по одному типу, T
», а x: T
говорит о том, что «х имеет тип T
».
Несколько аргументов могут иметь один и тот же обобщённый тип:
fn takes_two_of_the_same_things<T>(x: T, y: T) {
// ...
}
Мы можем написать версию, которая принимает несколько типов:
fn takes_two_things<T, U>(x: T, y: U) {
// ...
}
Обобщённые функции наиболее полезны в связке с «ограничениями по типажам», о которых мы расскажем в главе Типажи.
Обобщённые структуры
Вы также можете задать обобщённый тип для struct
:
struct Point<T> {
x: T,
y: T,
}
let int_origin = Point { x: 0, y: 0 };
let float_origin = Point { x: 0.0, y: 0.0 };
Аналогично функциям, мы также объявляем обобщённые параметры в <T>
, а затем
используем их в объявлении типа x: T
.