子类型(Subtyping)和型变(Variance)
子类型是隐式的,可以在类型检查或推断的任何阶段发生。
子类型仅限于两种情况:关于生命周期的型变和具有高阶生命周期的类型之间的型变。如果我们要从类型中擦除生命周期,那么唯一的子类型将是由类型相等引起的。
考虑以下示例:字符串字面量始终具有 '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 的子类型。
高阶 函数指针 和 trait 对象 具有另一种子类型关系。它们是通过替换高阶生命周期给出的类型的子类型。一些示例:
#![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;
}
}