I've noticed in multiple occasions that sometimes we may be dealing with multiple lifetimes, potentially invariant, but actually need only one for some API:
better_any::Tid<'a>
is only generic over a single lifetime'a
bevy
'sSystemInput
has a similar limitation- it's not that uncommon for people to want to create some kind of stacked environment struct, sort of like a singly linked list where links are mutable references (see e.g. this recent discussion on URLO)
All these cases could be solved by erasing multiple lifetimes into a single, "common denominator" lifetime, as long as we don't care about what was the specific lifetime but only that it was "big enough".
Practically speaking I think this could be done if we had a kind of type that closes over some existential lifetime. I'll refer to this kind of type with the syntax exists<'a: 'b> T<'a>
(potentially to bikeshed). In short this would be a type whose values can be values of type T<'a>
with any lifetime 'a
that is greater than the given 'b
. In this kind of types exists
would be a quantifier, just like for<'a>
, bounding 'a
, while 'b
would be free.
Of course these types would be useless if they weren't somehow usable like an instance of T<'a>
for some lifetime 'a
, but of course using 'b
as lifetime would be unsound, as that would be basically equivalent to make every lifetime covariant. Instead I believe each instance of these types should introduce a new anonymous lifetime in the current context, known to be bigger than 'b
but without other informations.
Note that currently we already a kind of type with this capability: trait objects. A dyn Trait + 'b
can hold values of any type implementing Trait
and having a lifetime greater than 'b
. We can exploit this to build a POC showing what an hypothetical exists
kind of type can do, however this is not really usable in practice for a couple of reasons:
- it suffers from the limitation of trait objects, like dynamic dispatch and requiring boxing in certain situations (even though we want to use it only for types that will all have the same shape and implementations at runtime!)
- the API requiring closures and the extra parameter for the implied bounds is pretty inconvenient.
What do you think?