Подтипы и вариативность
Подтипизация является неявной и может происходить на любом этапе проверки типов или вывода типов.
Подтипизация ограничена двумя случаями: вариативность по времени жизни и между типами с высшими рангами времён жизни. Если бы мы стёрли времена жизни из типов, то подтипизация была бы возможна только благодаря равенству типов.
Рассмотрим следующий пример: строковые литералы всегда имеют время жизни 'static.
Тем не менее, мы можем присвоить s переменной t:
#![allow(unused)] fn main() { fn bar<'a>() { let s: &'static str = "hi"; let t: &'a str = s; } }
Поскольку 'static переживает параметр времени жизни 'a, &'static str является
подтипом &'a str.
Указатели на функции и объекты трейтов с высшими рангами имеют другое отношение подтипов. Они являются подтипами типов, полученных путём подстановок времён жизни высшего ранга. Несколько примеров:
#![allow(unused)] fn main() { // Здесь 'a заменяется на 'static let subtype: &(for<'a> fn(&'a i32) -> &'a i32) = &((|x| x) as fn(&_) -> &_); let supertype: &(fn(&'static i32) -> &'static i32) = subtype; // Это аналогично работает для объектов трейтов let subtype: &(dyn for<'a> Fn(&'a i32) -> &'a i32) = &|x| x; let supertype: &(dyn Fn(&'static i32) -> &'static i32) = subtype; // Мы также можем подставить одно время жизни высшего ранга вместо другого let subtype: &(for<'a, 'b> fn(&'a i32, &'b i32)) = &((|x, y| {}) as fn(&_, &_)); let supertype: &for<'c> fn(&'c i32, &'c i32) = subtype; }
Вариативность
Вариативность — это свойство, которое обобщённые типы имеют по отношению к своим аргументам. Вариативность обобщённого типа в параметре описывает, как отношение подтипов для этого параметра влияет на отношение подтипов для самого типа.
F<T>является ковариантным поT, если из того, чтоT— подтипU, следует, чтоF<T>— подтипF<U>(подтипизация “проходит насквозь”)
F<T>является контравариантным поT, если из того, чтоT— подтипU, следует, чтоF<U>— подтипF<T>
F<T>является инвариантным поTв остальных случаях (не может быть выведено никакое отношение подтипов)
Вариативность типов автоматически определяется следующим образом
| Тип | Вариативность по 'a | Вариативность по T |
|---|---|---|
&'a T | ковариантный | ковариантный |
&'a mut T | ковариантный | инвариантный |
*const T | ковариантный | |
*mut T | инвариантный | |
[T] и [T; n] | ковариантный | |
fn() -> T | ковариантный | |
fn(T) -> () | контравариантный | |
std::cell::UnsafeCell<T> | инвариантный | |
std::marker::PhantomData<T> | ковариантный | |
dyn Trait<T> + 'a | ковариантный | инвариантный |
Вариативность других типов struct, enum и union определяется
путем анализа вариативности типов их полей. Если параметр используется
в позициях с разной вариативностью, то параметр является инвариантным.
Например, следующая структура является ковариантной по 'a и T и инвариантной по 'b, 'c,
и U.
#![allow(unused)] fn main() { use std::cell::UnsafeCell; struct Variance<'a, 'b, 'c, T, U: 'a> { x: &'a U, // Это делает `Variance` ковариантной по 'a, и // сделало бы её ковариантной по U, но U используется позже y: *const T, // Ковариантный по T z: UnsafeCell<&'b f64>, // Инвариантный по 'b w: *mut U, // Инвариантный по U, делает всю структуру инвариантной f: fn(&'c ()) -> &'c () // Одновременно ко- и контравариантный, делает 'c инвариантным // в структуре. } }
При использовании вне struct, enum или union, вариативность для параметров проверяется в каждом месте отдельно.
#![allow(unused)] fn main() { use std::cell::UnsafeCell; fn generic_tuple<'short, 'long: 'short>( // 'long используется внутри кортежа и в ковариантной, и в инвариантной позиции. x: (&'long u32, UnsafeCell<&'long u32>), ) { // Поскольку вариативность в этих позициях вычисляется отдельно, // мы можем свободно сужать 'long в ковариантной позиции. let _: (&'short u32, UnsafeCell<&'long u32>) = x; } fn takes_fn_ptr<'short, 'middle: 'short>( // 'middle используется и в ковариантной, и в контравариантной позиции. f: fn(&'middle ()) -> &'middle (), ) { // Поскольку вариативность в этих позициях вычисляется отдельно, // мы можем свободно сужать 'middle в ковариантной позиции // и расширять его в контравариантной позиции. let _: fn(&'static ()) -> &'short () = f; } }