This top part is mainly useful if you like solving lifetime puzzles. Something potentially useful is at the bottom.
The low-level cause (which does prevent this high-level unsoundness) is invariance. Namely, trait parameters are invariant. So in the case of a trait object, the method signature takes
&dyn MyTrait<Chars<'c>>, Chars<'c>
// ^^^^^^^^^
Because the underlined part is invariant, the lifetime has to be the entirety of the borrow 'c
. This is what ties the borrow of s
to the type of dyn_trait
:
Box<dyn MyTrait<Chars<'c>> + '_>
and the final piece of the puzzle is that trait objects always have a non-trivial drop, so from a low-level borrow checking perspective, there's a use of the borrow of s
when dyn_trait
goes out of scope.
More generally, if the following three ingredients are a recipe for borrow entanglement:
- A method taking different parameters with the same lifetime
- Something causing the lifetime to be invariant
- A non-trivial drop
The invariance of trait parameters means that a dyn MyTrait<Chars<'c>>
is saying, "I can only work with Chars<'a>
if 'a
is exactly 'c
". Contravariance would be "'c
or longer", etc.
There is a way to be more general without changing the trait: you can have a higher-ranked bound that says "I can work with Chars<'a>
for any 'a
". That would be this change:
- let dyn_trait: Box<dyn MyTrait<_>> = Box::new(X {});
+ let dyn_trait: Box<dyn for<'a> MyTrait<Chars<'a>>> = Box::new(X {});
Now the lifetimes in the method call don't have to be the same.
Whether the implementors you want can meet that bound or not is a different question, so this may or may not be a viable alternative.
In terms of @2e71828 solution, you will end up with the same borrowing situation as the OP if the iterator Item
type is something borrowed. The higher-ranked trait object type (commented in the playground) again may or may not be a viable workaround, depending on the MyTrait
implemention of the base type.