Can `'a outlives T` be implemented with implied bounds?

Implied Bounds on Stable

I've been messing around with implied bounds lately. In particular, I know that bounds like

  • for<'a> where <'a: 'lower> ...,
  • for<'a> where <'upper: 'a> ..., and
  • for<'a> where <Upper: 'a> ...

are all possible to implement via implied bounds. For instance,

trait ImpliedBounds<
  'a, 'lower, 'upper,
  __ImplyBound = (&'lower &'a (), &'a &'upper ()),
> { /* ... */ }

and &'upper () can be replaced by an Upper type parameter. I was trying to get a version working with a Lower type parameter, but it didn't work out.

I was aiming to reduce 'a outlives Lower (more precisely, "'a outlives the shortest lifetime in Lower") to something like for<'lower> where<Lower: 'lower /* implemented with an implied bound */> 'a: 'lower, which, semantically, can only be fulfilled if 'a outlives the shortest lifetime in Lower (if any), or 'a: 'static if Lower: 'static. (To prove the contrapositive, we assume that Lower (or, its shortest lifetime 'lower_shortest) strictly outlives 'a, in which case they're strictly separated by some lifetime 'lower such that Lower: 'lower_shortest and 'lower_shortest: 'lower but 'a: 'lower does not hold since 'lower strictly outlives 'a.)

My attempt was variations of the following:

use core::marker::PhantomData;

struct ARef<T: ?Sized>(PhantomData<*const T>);

trait TypeImpliedBounds<
  'a, Lower: ?Sized, Upper: ?Sized,
  // Doesn't work because `'a: 'lower` failing to hold just means
  // that `'lower` lifetime isn't considered (it's just a normal implied bound)
  __ImplyBound = (&'a Upper, for<'lower> fn(&'lower Lower, &'lower &'a ())),
>: for<'helper_lower> Helper<'helper_lower, 'a, Lower>
{
    type SometimesWellFormed: ?Sized;
}

// Implementation (below) is not general enough; the bound isn't properly conditional.
trait Helper<
  'helper_lower, 'a, Lower: ?Sized,
  __ImplyBound = &'helper_lower Lower,
>
where
    'a: 'helper_lower,
{}

impl<'a: 'lower, 'lower, 'upper>
    TypeImpliedBounds<'a, &'lower (), &'upper ()>
for &'lower ARef<&'upper ()>
{
    type SometimesWellFormed = &'lower &'a &'upper ();
}

impl<'helper_lower, 'a, Lower: ?Sized, T: ?Sized>
    Helper<'helper_lower, 'a, Lower>
for T
where
    'a: 'helper_lower,
{}

That attempt at a conditional 'a: 'helper_lower bound did not work.

Is this just not possible with the current trait solver on stable Rust? I read somewhere (IIRC on URLO or IRLO) that full where bounds would likely provide conditional bounds and potentially even unions/disjunctions, the latter of which seems like a daunting implication. (Maybe I'm wrong about the latter being difficult to handle -- no need to actually solve SAT, "just" need to set a deterministic limit on the permitted complexity -- but it seems complicated.)

Hopes for future Rust

Once the next gen trait solver is complete and for<...> where<...> ... bounds (or similar) eventually get added to nightly, I think that expressing the requirement that "'a outlives the shortest lifetime in T" (where "the shortest lifetime" falls back to 'static if T: 'static) would be possible in a trait bound. It would, however, be awkward to express.

Moreover, I think the requirement that "'t is the shortest lifetime in T (falling back to 'static)" would be expressible, but pulling a lifetime 't out of thin air given a T would still not be possible.

I would really love if the shortest lifetime in a generic parameter T could be extracted in some way, perhaps with something like 'bikeshed_shortest_lt<T>. (Maybe existential lifetimes would provide a roundabout solution -- I haven't looked into the specifics of their semantics -- but I might as well discuss the direct solution as well.)

Tangent about motivation

This would be able to prevent an unnecessary lifetime from infecting everything in a crate I'm working on right now. I would love to perform lifetime erasure instead, but since I'm working with non-'static data, I can't simply transmute the lifetime to 'static to erase it (the resulting type might not be well-formed); I would need to transmute the lifetime to 'bikeshed_shortest_lt<T>. I could instead try to erase it to some MaybeUninit<[u8; SIZE]> abomination, but using size_of::<T>() and align_of::<T>() to influence a struct's layout requires the nightly-only generic_const_exprs. The alternative on stable to infecting everything with a lifetime would infect everything with a generic parameter or two to indicate size and alignment for an aligned MaybeUninit<[u8; SIZE]> buffer.

Even once something like generic_const_exprs is stabilized, performing lifetime erasure to MaybeUninit bytes seems substantially less ideal than unsafely changing a lifetime.

More about 'bikeshed_shortest_lt

That exact syntax might not work well, since unless 'bikeshed_shortest_lt were (at least) a weak keyword like 'static, then &'bikeshed_shortest_lt<T>::maybe_crate::Foo (and similar) would be at risk of being ambiguous with associated types (if not for the "ambiguous associated type" error, which AFAIK is considered to merely be a current limitation rather than a load-bearing error).

As for the implementation, requiring a lifetime or type to outlive or be outlived by 'bikeshed_shortest_lt<T> (which is 4 possibilities) could (naively) be desugared to one of four bounds, utilizing for<...> where<...> ... if needed. (In reality, the compiler could internally implement it more efficiently.)

  • 'a: 'bikeshed_shortest_lt<T> should hold iff for<'lower> where <T: 'lower> 'a: 'lower,
  • 'bikeshed_shortest_lt<T>: 'a should hold iff T: 'a,
  • U: 'bikeshed_shortest_lt<T> should hold iff for<'lower> where <T: 'lower> U: 'lower,
  • 'bikeshed_shortest_lt<T> outlives U (if that needs to be specified) would be expressed as 'bikeshed_shortest_lt<T>: 'bikeshed_shortest_lt<U> and should hold iff for<'lower> where <U: 'lower> 'bikeshed_shortest_lt<T>: 'lower, which is equivalent to for<'lower> where <U: 'lower> T: 'lower.

This idea feels too half-baked to even make a pre-RFC, but I'd love to hear any thoughts about it. (And I'm still curious whether "'a outlives the shortest lifetime in T" can be expressed with implied bounds on stable.)

Yes, if it was recently then this might have been my comment here. Or somewhere else where I had learned about it IDK

I believe this kind of functionality isn’t necessarily anything the current new-traitsolver developments can help with at all.

I believe I have seen something like this discussed somewhere before; gotta look at the forums. I believe it was motivated by some use cases involving dyn SomeTrait<T> in a struct, where you would like to be able to define specifically a struct like struct Foo<T>(Box<dyn SomeTrait<T> + 'bikeshed_shortest_lt<T>>) instead of being forced to give the struct another lifetime bound.[1]


  1. Imagine that SomeTrait<T> is some kind of trait abstracting over different container-types – say LinkedList<T> or VecDeque<T> – for items of generic type T so the only actual lifetimes do come from the type T and nothing else, otherwise – apart from the item type – the type implementing SomeTrait is essentially 'static- ↩︎

3 Likes

I’ve found at least one such discussion:

1 Like

Yup, that was exactly where. Though I conflated "union" as in "minimum" with "union" as in allowing lifetime bounds that hold for more than a simple range of lifetimes, like "either 'a: 'b or 'c: 'a" where 'b and 'c are distinct lifetimes with 'b: 'c. Maybe both would become possible for all I know, which I cannot see going well.

It's been stated here (and I believe elsewhere) that...

fixing it relies on where-bounds on binders which are blocked on the next-generation trait solver

...though maybe that just means "new trait solver before we even attempt it."

3 Likes

I’d probably read it as that, but I’m not entirely confident. Thanks for the relevant link, anyway.