子类型化和协变
子类型化是隐式的,可以在类型检查或推断阶段发生。 子类型化只限于两种情况: 类型和具有更高阶生命周期的类型之间的协变。 如果抹去类型的生命周期,则唯一的子类型是类型相等。
译注:子类型的这个概念与面向对象的特性相同,如果类型 A 是类型 B 的子类型,那么意味着 A 类型的值可以被 B 类型变量接收。 其主要的应用场景将值在不同类型间进行传递时,如果安全,不必手动转换,并且这种转换仅限于对类型生命周期的范围的调整,在这里被称为 '协变' 。
思考以下示例: 字符串字面量 "hi" 始终具有 '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() { // 这里将 'a 取代为 'static let subtype: &(for<'a> fn(&'a i32) -> &'a i32) = &((|x| x) as fn(&_) -> &_); let supertype: &(fn(&'static i32) -> &'static i32) = subtype; // 这同样适用于 trait 对象 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; }
协变
协变是泛型类型与其参数相关的一个特性。 泛型类型在参数上的 协变 性,其表示了参数的子类型化如何影响类型的子类型化的行为。
- 如果
T
是U
的子类型,则F<T>
是 协变的 的,这意味着F<T>
是F<U>
的子类型 ( 子类型关系 "传递" )。 - 如果
T
是U
的子类型,则F<T>
是 逆变的 的,这意味着F<U>
是F<T>
的子类型。 - 否则,
F<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; } }