This happens because of variance. Specifically, it's because &'a T
(and consequently PhantomData<&'a T>
) is covariant in 'a
, but dyn M<'a, T, I>
is invariant in 'a
.
Variance is a subtle and rather abstract subject but here's a short, slightly wrong, mostly workable version: by using 'a
in the trait object dyn M
you have told the compiler that MyIterable<'a>
may contain something analogous to a Cell<&'a T>
. It is no longer safe to treat a MyIterable<'long>
as if it were "merely" a MyIterable<'short>
, because doing so might allow someone to call set
and sneak a 'short
reference into that hypothetical Cell
which is supposed to only hold a 'long
reference. The answers to this question include perhaps a more approachable long-form introduction to variance.
In your situation, invariance comes from introducing a trait object rather than a mutable reference. Traits (including trait objects and bounded type parameters) are always treated conservatively as invariant. Even though a trait might only be implemented by covariant types, there is no way to make a trait itself covariant, so if you write code that assumes covariance for M<'a>
, anyone who came along and implemented M<'a>
for a type not covariant in 'a
could break your code by accident (or by malice).
What's interesting about variance is that it is one of the only things Rust will infer about your struct from its contents which other code can observe. Most of the time, adding a new field to a struct is a backwards-compatible change. But if the new field uses an existing type parameter with a different kind of variance, the variance of the whole struct will change, which can break code that was relying (possibly without your knowledge) on the old variance. The other things that are like variance in this respect are drop behavior and auto traits. These are also the reasons to use PhantomData
in structs, and using PhantomData
with an inappropriate type parameter can cause similar compatibility hazards.
Let's back up. When you are new to Rust, you may occasionally encounter very complicated problems not because you're doing something truly complicated, but because you've unintentionally complicated things for yourself by using lifetimes incorrectly. Lifetime parameters should be used sparingly and reused even less. Most of the time, you want every &
to have a new, fresh lifetime, which allows the compiler maximal freedom to assign lifetimes. This is what lifetime elision does by default, so in a lot of Rust code you will not see any lifetimes at all. If you try to write lifetimes everywhere, and especially if you use the same lifetime several times in the same type (as in &'a self
, where Self
is a type involving 'a
, and dyn M<'a, T, I> + 'a
) you are very likely to find yourself at a dead end.
What to do
- Don't create a lifetime parameter unless you know you need one.
- When you do need a lifetime, make it distinct from all other lifetimes, unless you know it needs to be the same.
Here's one possibility:
impl<'a, 'r, T, I> std::fmt::Debug for MyIterable<'a, 'r, T, I> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let x = self.as_my_iterator();
Ok(())
}
}
// It may make sense to eliminate `'r` too, depending on what M means
pub trait M<'r, T: 'r, I> {}
pub struct MyIterable<'a, 'r, T, I> {
coefficients: Box<dyn M<'r, T, I> + 'a>,
}
pub struct MyIterator<'a, T> {
pub(crate) coefficients: &'a [T],
}
pub trait AsMyIterator<T> {
fn as_my_iterator(&self) -> Result<MyIterator<'_, T>, ()>;
}
impl<'a, 'r, T, I> AsMyIterator<T> for MyIterable<'a, 'r, T, I> {
fn as_my_iterator(&self) -> Result<MyIterator<'_, T>, ()> {
todo!();
}
}
See also