子类型化和协变

子类型化是隐式的,可以在类型检查或推断阶段发生。 子类型化只限于两种情况: 类型和具有更高阶生命周期的类型之间的协变。 如果抹去类型的生命周期,则唯一的子类型是类型相等。

译注:子类型的这个概念与面向对象的特性相同,如果类型 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;
}

协变

协变是泛型类型与其参数相关的一个特性。 泛型类型在参数上的 协变 性,其表示了参数的子类型化如何影响类型的子类型化的行为。

  • 如果 TU 的子类型,则 F<T>协变的 的,这意味着 F<T>F<U> 的子类型 ( 子类型关系 "传递" )。
  • 如果 TU 的子类型,则 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协变的不变的

其他 structenumunion 类型的协变性是通过查看它们字段类型的协变性来决定的。 如果在不同协变位置使用了参数,则该参数是不变的。 例如,下面的结构体在 'aT 上是协变的,在 'b'cU 上是不变的。

#![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 不变
}
}

当在 structenumunion 之外使用时,参数的协变性在每个位置上分别进行检查。

#![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;
}
}