Rust中Box类型的协方差

时间:2019-03-16 19:47:37

标签: rust covariance

在阅读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<A>) {}

fn main() {
    let c = C;
    let b: Box<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>是协变的,而唯一的用例是不变的?

2 个答案:

答案 0 :(得分:4)

Rust中的子类型化和方差意味着什么

Nomicon不是完全抛光的文档。目前,该回购协议中最近发行的10个问题中有5个专门根据其标题来处理子类型或差异。 Nomicon中的概念可能需要大量的精力,但信息通常都存在。

首先,请查看一些初始段落(重点是我的段落):

  

Rust中的子类型与其他语言中的子类型有些不同。这使得给出简单示例变得更加困难,这是一个问题,因为子类型(尤其是方差)已经很难正确理解。

     

为简单起见,本节将考虑对Rust语言进行小的扩展,以便添加新的和更简单的子类型关系。在此简单系统下建立概念和问题后,我们将其与Rust中子类型的实际发生方式联系起来。

然后继续显示一些基于特征的代码。重申这一点,此代码不再是不是 Rust代码。特质不能在Rust中形成子类型!

稍后,有这句话:

  

首先,基于生存期的子类型引用是Rust中子类型的整个重点。我们具有子类型化的唯一原因是,我们可以在预期寿命短的地方传递寿命长的东西。

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显然是A的“子类型”

不是。

  

Box在其输入上是协变的

也就是说,协方差仅适用于生命周期。

  

我不知道为什么它不起作用或者为什么它不执行任何类型的强制操作。

Why doesn't Rust support trait object upcasting?

  

为什么他们认为Box<T>是协变的

因此,对于Rust中适用于差异的事物。

另请参见

答案 1 :(得分:0)

补充一点:

我认为这里的混淆主要是由于一个常见的误解,即当我们说 Foo<T> 时,总是假定 T 是一个拥有类型。实际上,T 可以引用一个引用类型,例如 &i32

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

<块引用>

差异是指更复杂类型之间的子类型与其组件之间的子类型之间的关系。

在 Rust 中,正如其他人指出的那样,子类型仅适用于生命周期。子特性关系不定义子类型:如果特性 A 是特性 B 的子特性,并不意味着 AB 的子类型。

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

'b