I'm going to try to demonstrate some practical implications of having lifetime bound on a trait. It will touch on some moderately advanced topics. TL;DR: there's a good chance you don't need or want the bound.
First a couple of technical notes.
-
An outlives relation on a type, T: 't
, is defined syntactically, and requires that any lifetimes 'x
found within T
meet a 'x: 't
bound.
-
In order to be able to type erase M
to dyn Trait + 'm
, the bound M: 'm
must hold. You can think of the + 'lifetime
on a dyn Trait
as being a bound you know the erased base type meets.
Here's a (non-practical) example where the bound on the trait matters. If you delete the + 'de
bound on the trait, the example
function will fail. The reason is that the generic M
might not meet the 'de
bound anymore -- it might contain a lifetime that doesn't meet the bound, since the trait didn't require that in the implementation. Note that this is true whether M
contains 'de
or not. (Like the help says, you could put the bound on the function instead if you wanted.)
Here's @jofas' example, slightly modified. The example
is now a bit more practical (but still somewhat contrived). If you delete the + 'de
bound on the trait, this example
function will also fail.
The reason why the 'de
bound is load bearing in this example is related to the syntactical nature of outlive bounds. If M: 'de
, it doesn't contain any lifetime 'short
which doesn't meet the bound 'de
. That means it's impossible for the trait implementation of MyDeserialise<'de> for M
to name a too-short lifetime when defining the associated type Error
. The compiler can therefore conclude that <M as Deserialise<'de>>::Error: 'de
.
This RFC has more details about outlives relations and "projections" (outlives relations of associated types in a generic context), though it's a bit dense.
Now let's switch gears a bit and see some implications of the bound from the implementor's perspective. Here, we've implemented the trait with the bound for a type that also has an independent lifetime 's
. If you remove where 's: 'de
, the implementation won't compile anymore. This demonstrates what I was saying about 'short
above. The 's: 'de
bound is required even though in the implementing type, 's
didn't have anything to do with 'de
. This often doesn't cause too many problems anyway, when the lifetimes are covariant (as they are in the example). Perhaps because if there's code that cared about how long the implementing type lived anyway, it would have had to deal with the case where 's: 'de
already.
But let's look at a case where the implementor has an independent lifetime, and doesn't have 'de
. As before, the where 's: 'de
is required on the implementation due to the bound on the trait. But now the implementing type is completely unrelated to 'de
. The implementation could work for any 'de
, just like it would for a T: 'static
implementor. But the 's: 'de
bound is still required because it is on the trait. This sometimes can cause problems.
For example, if you uncomment the hrtb
call, you'll see that it fails. But if you remove the bound on the trait and the 's: 'de
bound from the implementation, it succeeds.
Related problems come up around GATs fairly often. The workarounds are more involved than I want this comment to be, but here's an article about that. The workarounds are required when the trait depends on the Self: 'de
bound, but you still want higher-ranked trait bounds to be functional. They emulate a conditional higher-ranked bound: for<'de where M: 'de> ...
.
The workaround notably avoids putting the bound on the trait, as that defeats the higher-ranked goal in the same way as the example above.
Now that I've demonstrated a potential pitfall or limitation of putting the lifetime bound on the trait, let's take a step back and ask: what was gained by adding the bound? The trait definition didn't need it, and our examples where we took advantage of it were somewhat contrived and could be worked around locally. In my experience, if you have a lifetime on a trait and a generic type that implements it for one specific lifetime, you usually still don't care about the lifetime of the implementor -- and if you do, you can use a distinct lifetime for that bound.
But when we cared about HRTBs -- about meeeting a bound for all lifetimes -- the bound on the trait got in the way.
I think a lot of lifetime bounds get added to traits out of instinct or misunderstanding, and not an actual need. They also have some odd niche effects which I didn't go into. The main exception is a 'static
bound, which is required for things like the Any
trait.
Therefore, if you don't have an explicit reason to add such a bound, my advice is to not add it. (And if you think you have an explicit reason, test it, as it may not do what you think.)