子类型化和变型
子类型化是隐式的,并且可以发生在类型检查或推断的任何阶段。
子类型化被限制在两种情况:关于生命周期的变型以及具有高阶生命周期的类型之间。如果我们从类型中擦除生命周期,那么唯一的子类型化将是由于类型相等性。
考虑以下示例:字符串字面量始终具有 '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() { // Here 'a is substituted for 'static let subtype: &(for<'a> fn(&'a i32) -> &'a i32) = &((|x| x) as fn(&_) -> &_); let supertype: &(fn(&'static i32) -> &'static i32) = subtype; // This works similarly for trait objects let subtype: &(dyn for<'a> Fn(&'a i32) -> &'a i32) = &|x| x; let supertype: &(dyn Fn(&'static i32) -> &'static i32) = subtype; // We can also substitute one higher-ranked lifetime for another let subtype: &(for<'a, 'b> fn(&'a i32, &'b i32))= &((|x, y| {}) as fn(&_, &_)); let supertype: &for<'c> fn(&'c i32, &'c i32) = subtype; }
变型
变型是泛型类型相对于其参数所具有的属性。 泛型类型在参数中的变型是参数的子类型化如何影响类型的子类型化。
- 如果
T
是U
的子类型意味着F<T>
是F<U>
的子类型(子类型化“传递通过”),则F<T>
在T
上是协变的
- 如果
T
是U
的子类型意味着F<U>
是F<T>
的子类型,则F<T>
在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, // This makes `Variance` covariant in 'a, and would // make it covariant in U, but U is used later y: *const T, // Covariant in T z: UnsafeCell<&'b f64>, // Invariant in 'b w: *mut U, // Invariant in U, makes the whole struct invariant f: fn(&'c ()) -> &'c () // Both co- and contravariant, makes 'c invariant // in the struct. } }
当在 struct
、enum
或 union
外部使用时,参数的变型在每个位置单独检查。
#![allow(unused)] fn main() { use std::cell::UnsafeCell; fn generic_tuple<'short, 'long: 'short>( // 'long is used inside of a tuple in both a co- and invariant position. x: (&'long u32, UnsafeCell<&'long u32>), ) { // As the variance at these positions is computed separately, // we can freely shrink 'long in the covariant position. let _: (&'short u32, UnsafeCell<&'long u32>) = x; } fn takes_fn_ptr<'short, 'middle: 'short>( // 'middle is used in both a co- and contravariant position. f: fn(&'middle ()) -> &'middle (), ) { // As the variance at these positions is computed separately, // we can freely shrink 'middle in the covariant position // and extend it in the contravariant position. let _: fn(&'static ()) -> &'short () = f; } }