为什么生命周期强制对结构有效但对特征无效?

Why does lifetime coercion work with structs but not with traits?

编译器不接受以下代码:

struct Struct<'a, 'b: 'a> {
    value: &'a dyn Value<'b>,
}

impl<'a, 'b: 'a> Struct<'a, 'b> {
    fn foo(&self) {
        UnitedLifetime(self.value);
    }
}

struct UnitedLifetime<'a>(&'a dyn Value<'a>);

trait Value<'a> {}

它产生以下错误:

error[E0495]: cannot infer an appropriate lifetime for automatic coercion due to conflicting requirements
 --> src/lib.rs:7:24
  |
7 |         UnitedLifetime(self.value);
  |                        ^^^^^^^^^^
  |
note: first, the lifetime cannot outlive the lifetime `'a` as defined here...
 --> src/lib.rs:5:6
  |
5 | impl<'a, 'b: 'a> Struct<'a, 'b> {
  |      ^^
note: ...so that reference does not outlive borrowed content
 --> src/lib.rs:7:24
  |
7 |         UnitedLifetime(self.value);
  |                        ^^^^^^^^^^
note: but, the lifetime must be valid for the lifetime `'b` as defined here...
 --> src/lib.rs:5:10
  |
5 | impl<'a, 'b: 'a> Struct<'a, 'b> {
  |          ^^
note: ...so that the types are compatible
 --> src/lib.rs:7:24
  |
7 |         UnitedLifetime(self.value);
  |                        ^^^^^^^^^^
  = note: expected `dyn Value<'_>`
             found `dyn Value<'b>`

如果我使用 struct Value<'a>(&'a u8); 而不是 traits,它会编译。

我想要一个 UnitedLifetime 的生命周期与 Struct 一样长,它又具有其他对象的生命周期,这意味着 'a: '_'b: '_ 总是正确的。

我做错了什么?这是编译器错误吗?

原因是差异。

dyn Trait<'b>'b 上是不变的。也就是说,换句话说,'b 必须恰好是 'b 而不是任何其他生命周期,既不短也不长。因此,编译器不能使用“'a'b 中较短的那个”。

Trait objects are always invariant over their generic arguments.

另一方面,

u8 没有引用 'b&'a u8'a 上是协变的。这意味着它可以缩小,因此编译器可以使用“较短的”。

如果您将 dyn Value<'b> 替换为在 'b 上不变的结构:

,您会发现方差是问题而不是特征
struct Value<'a> {
    // `fn(&'a ()) -> &'a ()` is both covariant over `'a` (because of the parameter)
    // and contravariant over it (because of the return type). covariant+contravariant=invariant.
    _marker: PhantomData<fn(&'a ()) -> &'a ()>
}

Playground.


要解释为什么 trait 对象在其参数上是不变的,请检查以下示例:

trait Value<'a> {
    fn set_value(&mut self, v: &'a i32);
}

struct S<'a> {
    value: Box<&'a i32>,
}

impl<'a> Value<'a> for S<'a> {
    fn set_value(&mut self, v: &'a i32) {
        *self.value = v;
    }
}

fn foo<'a>(v: &mut dyn Value<'a>) {
    let new_value: i32 = 0;
    let new_value_ref: &i32 = &new_value;
    v.set_value(new_value_ref);
}

fn main() {
    let mut s = S { value: Box::new(&0) };
    foo(&mut s);
    dbg!(&**s.value);
}

Playground.

如果特征对象在其参数上是协变的,foo() 将编译。我们只是缩短了生命周期——从 'a 到局部变量的生命周期 new_value,它总是更短。

但这会很糟糕,因为在退出 foo() 后,new_value 将被销毁,而 s.value 将指向释放的内存 - 释放后使用!

这就是可变引用不变的原因。但是 trait 对象可以包含 任何东西 - 包括可变引用 - 所以它们的任何参数都必须是不变的!

注意: 我说如果我们在 'a 上协变我们将编译,我是不准确的,因为我们仍然采用 &mut self 和因此在 S<'a> 上不变,并且在 'a 上传递不变。但是我们可以调整此示例以使用内部可变性并采用协变的 &self 。我没有这样做是为了不让事情变得更复杂。