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> ..., andfor<'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 ifffor<'lower> where <T: 'lower> 'a: 'lower,'bikeshed_shortest_lt<T>: 'ashould hold iffT: 'a,U: 'bikeshed_shortest_lt<T>should hold ifffor<'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 ifffor<'lower> where <U: 'lower> 'bikeshed_shortest_lt<T>: 'lower, which is equivalent tofor<'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.)