05-子类型与可变性

子类型与可变性 — The Rust Reference

译文 · 基于 The Rust Reference

原文链接: https://doc.rust-lang.org/reference/subtyping.html

r[subtype]

子类型与可变性

r[subtype.intro] 子类型是隐式的,可以发生在类型检查或推断的任何阶段。

r[subtype.kinds] 子类型仅限于两种情形:关于生命周期的型变,以及带有高阶生命周期的类型之间。若我们从类型中抹去生命周期,则唯一的子类型关系将来自类型相等。

考虑下面的例子:字符串字面量总是具有 'static 生命周期。尽管如此,我们仍可以将 s 赋给 t:

1
2
3
4
fn bar<'a>() {
    let s: &'static str = "hi";
    let t: &'a str = s;
}

由于 'static 长于生命周期参数 'a,&'static str 是 &'a str 的子类型。

r[subtype.higher-ranked] 高阶函数指针和 trait 对象还有另一种子类型关系。它们是通过对高阶生命周期进行代换所得到类型的子类型。一些例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 此处用 'static 代换 'a
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;

r[subtyping.variance]

型变

r[subtyping.variance.intro] 型变是泛型类型相对于其参数所具有的性质。泛型类型在某个参数上的 型变 描述了该参数的子类型关系如何影响该类型的子类型关系。

r[subtyping.variance.covariant]

  • 若 T 是 U 的子类型蕴含 F<T> 是 F<U> 的子类型(子类型关系「穿透」),则 F<T> 在 T 上是 协变 的

r[subtyping.variance.contravariant]

  • 若 T 是 U 的子类型蕴含 F<U> 是 F<T> 的子类型,则 F<T> 在 T 上是 逆变 的

r[subtyping.variance.invariant]

  • 否则 F<T> 在 T 上是 不变 的(无法推导出子类型关系)

r[subtyping.variance.builtin-types] 类型的型变按如下方式自动确定

类型在 '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协变不变

r[subtyping.variance.user-composite-types] 其他 struct、enum 和 union 类型的型变通过查看其字段类型的型变来决定。若该参数在具有不同型变的位置上被使用,则该参数是不变的。例如下面的结构体在 'a 和 T 上协变,在 'b、'c 和 U 上不变。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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 上不变,使整个结构体在 U 上不变

    f: fn(&'c ()) -> &'c () // 既协变又逆变,使 'c 在结构体中不变
}

r[subtyping.variance.builtin-composite-types] 当用在 struct、enum 或 union 之外时,参数的型变在每个位置分别检查。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
## 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;
}
最后修改 August 21, 2026: 更新 (76fc81a2e)