Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Подтипы и вариативность

Подтипизация является неявной и может происходить на любом этапе проверки типов или вывода типов.

Подтипизация ограничена двумя случаями: вариативность по времени жизни и между типами с высшими рангами времён жизни. Если бы мы стёрли времена жизни из типов, то подтипизация была бы возможна только благодаря равенству типов.

Рассмотрим следующий пример: строковые литералы всегда имеют время жизни '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;
}
}