如果 Box<dyn B> 不是 Box<dyn A> 的子类型,其中 B: A,那么 Box 是协变的是什么意思?

What does it mean that Box is covariant if Box<dyn B> is not a subtype of Box<dyn A> where B: A?

阅读 the subtyping chapter of the Nomicon 后,我无法理解类型参数的协变性。特别是对于Box<T>类型,描述为:T is covariant.

但是,如果我写这段代码:

trait A {}
trait B: A {}

struct C;
impl A for C {}
impl B for C {}

fn foo(v: Box<dyn A>) {}

fn main() {
    let c = C;
    let b: Box<dyn B> = Box::new(c);
    foo(b);
}

(Playground)

error[E0308]: mismatched types
  --> src/main.rs:13:9
   |
13 |     foo(b);
   |         ^ expected trait `A`, found trait `B`
   |
   = note: expected type `std::boxed::Box<(dyn A + 'static)>`
              found type `std::boxed::Box<dyn B>`

B 显然是 A 的“子类型”,并且 Box 在其输入上是协变的。我不知道为什么它不起作用或者为什么它不会进行任何类型的强制转换。为什么他们认为 Box<T> 是协变的,而唯一的用例是不变量?

Rust 中的子类型和方差是什么意思

Nomicon 不是一份完全完善的文件。现在,该回购协议中最近的 10 期中有 5 期专门根据其标题单独处理子类型或差异。 Nomicon 中的概念可能需要大量努力,但信息通常都在那里。

首先,检查一些初始段落(强调我的):

Subtyping in Rust is a bit different from subtyping in other languages. This makes it harder to give simple examples, which is a problem since subtyping, and especially variance, are already hard to understand properly.

To keep things simple, this section will consider a small extension to the Rust language that adds a new and simpler subtyping relationship. After establishing concepts and issues under this simpler system, we will then relate it back to how subtyping actually occurs in Rust.

然后它继续显示一些 trait-based 代码。重申一下,这段代码不再是而不是 Rust 代码;特征在 Rust 中不形成子类型!

后来,有这句话:

First and foremost, subtyping references based on their lifetimes is the entire point of subtyping in Rust. The only reason we have subtyping is so we can pass long-lived things where short-lived things are expected.

Rust 的子类型概念仅适用于生命周期

子类型和方差的例子是什么?

变体生命周期

这是一个在 Box.

中工作的子类型和生命周期变化的例子

失败案例

fn smaller<'a>(v: Box<&'a i32>) {
    bigger(v)
}

fn bigger(v: Box<&'static i32>) {}
error[E0308]: mismatched types
 --> src/lib.rs:2:12
  |
2 |     bigger(v)
  |            ^ lifetime mismatch
  |
  = note: expected type `std::boxed::Box<&'static i32>`
             found type `std::boxed::Box<&'a i32>`
note: the lifetime 'a as defined on the function body at 1:12...
 --> src/lib.rs:1:12
  |
1 | fn smaller<'a>(v: Box<&'a i32>) {
  |            ^^
  = note: ...does not necessarily outlive the static lifetime

一个工作案例

fn smaller<'a>(v: Box<&'a i32>) {}

fn bigger(v: Box<&'static i32>) {
    smaller(v)
}

不变的生命周期

这是一个有效的案例:

struct S<'a>(&'a i32);

fn smaller<'a>(_v: &S<'a>, _x: &'a i32) {}

fn bigger(v: &S<'static>) {
    let x: i32 = 1;
    smaller(v, &x);
}

将所有引用更改为可变引用的相同代码将失败,因为可变引用是不变的:

struct S<'a>(&'a mut i32);

fn smaller<'a>(_v: &mut S<'a>, _x: &'a mut i32) {}

fn bigger(v: &mut S<'static>) {
    let mut x: i32 = 1;
    smaller(v, &mut x);
}
error[E0597]: `x` does not live long enough
 --> src/lib.rs:7:16
  |
7 |     smaller(v, &mut x);
  |     -----------^^^^^^-
  |     |          |
  |     |          borrowed value does not live long enough
  |     argument requires that `x` is borrowed for `'static`
8 | }
  | - `x` dropped here while still borrowed

解决具体问题

B is clearly a "subtype" of A

不是。

Box is covariant over its input

是的,协方差只适用于生命周期。

I don't know why it doesn't work or why it won't do any type coercion.

Why doesn't Rust support trait object upcasting?

对此进行了介绍

Why would they consider Box<T> to be covariant

因为它是,对于应用方差的 Rust 中的东西。

另见

补充一点:

我认为这里的混淆主要是由于一个普遍的误解,即当我们说 Foo<T> 时,T 总是被假定为一个拥有的类型。其实T可以引用一个引用类型,比如&i32.

关于(co)方差,维基百科将其定义为:

Variance refers to how subtyping between more complex types relates to subtyping between their components.

在 Rust 中,正如其他人所指出的,子类型化仅适用于生命周期。子特征关系不定义子类型:如果特征 A 是特征 B 的子特征,并不意味着 AB.[=24 的子类型=]

生命周期之间的子类型化示例:共享引用(例如 &'a i32)是另一个共享引用(例如 &'b i32)的子类型,当且仅当前者的生命周期超过后者的生命周期( 'a 长于 'b)。下面是一些演示它的代码:

fn main() {
    let r1: &'static i32 = &42;
    
    // This obviously works
    let b1: Box<&'static i32> = Box::new(r1);
    
    // This also works
    // because Box<T> is covariant over T
    // and `&'static i32` is a subtype of `&i32`.
    // NOTE that T here is `&i32`, NOT `i32`
    let b2: Box<&i32> = Box::new(r1);
    
    
    let x: i32 = 42;
    let r2: &i32 = &x;
    
    // This does NOT work
    // because `&i32` is NOT a subtype of `&'static i32`
    // (it is the other way around)
    let b3: Box<&'static i32> = Box::new(r2);
}