The following code is not accepted by the compiler:
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> {}
It produces the following error:
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>`
If I use struct Value<'a>(&'a u8);
instead of traits, it compiles.
I want to have an UnitedLifetime
that has a lifetime as long as Struct
, which in turn has lifetimes for other objects, which means that 'a: '_
and 'b: '_
are always true.
What am I doing wrong? Is this a compiler bug?
The reason is variance.
dyn Trait<'b>
is invariant over 'b
. That means, in other words, that 'b
must be exactly 'b
and not any other lifetime, neither shorter nor longer. Thus, the compiler cannot use "the shorter of 'a
and 'b
".
Trait objects are always invariant over their generic arguments.
u8
, on the other hand, has no reference to 'b
. &'a u8
is covariant over 'a
. That means that it can shrink, and thus the compiler can use "the shorter of".
You can see that variance is the problem and not the trait if you'll replace dyn Value<'b>
with a struct that is invariant over '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 ()>
}
To explain why trait objects are invariant over their parameters, inspect the following example:
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);
}
If trait objects were covariant over their parameters, foo()
would compile. We only shrink the lifetime - from 'a
to the lifetime of the local variable new_value
, which is always shorter.
But this would be terrible, since after exiting foo()
, new_value
would be destroyed, and s.value
would point to freed memory - use-after-free!
This is why mutable references are invariant. But trait objects can contain anything - including mutable references - so any parameter of them has to be invariant!
Note: I wasn't accurate when saying that if we would be covariant over 'a
we will compile, because we still take &mut self
and thus invariant over S<'a>
and, transitively, over 'a
. But we can adjust this example to use interior mutability and take &self
which is covariant. I did not do that in order to not complicate things more.