Influences from trait lifetime bounds

When the trait itself has lifetime bounds, those bounds may influence the behavior of dyn Trait lifetime elision. Where and how the influence does or does not take place is not properly documented, but we'll cover some cases here.

The way trait object lifetime defaults behave in these scenarios is not intuitive, and perhaps even arbitrary. But to be clear, you will probably never need to actually know the exact rules. Traits with exotic lifetime bounds are rare, and should you actually encounter one, you can usually choose to be explicit instead of trying to figure out what lifetime is the default when elided.

Which is to say, this subsection is more of an exploration of the compiler's current behavior than something useful to learn. If you're trying to learn practical Rust, you should probably just skip it.

A very high level summary is:

  • Trait bounds introduce implied bounds on the trait object lifetimes
  • Elision in the presence of non-'static trait lifetime bounds is arbitrary, so prefer to be explicit
  • Prefer not to add non-'static lifetime bounds to your own object safe traits
    • Avoid multiple lifetime bounds in particular

This section is also non-exhaustive. Given how many exceptions I have ran across, take my assertive statements in this section with a grain of salt.

Trait lifetime bounds create an implied bound

The trait bound creates an implied bound on the dyn Trait lifetime:

#![allow(unused)]
fn main() {
pub trait LifetimeTrait<'a, 'b>: 'a {}

pub fn f<'b>(_: Box<dyn LifetimeTrait<'_, 'b> + 'b>) {}

fn fp<'a, 'b, 'c>(t: Box<dyn LifetimeTrait<'a, 'b> + 'c>) {
    // This compiles which indicates an implied `'c: 'a` bound
    let c: &'c [()] = &[];
    let _: &'a [()] = c;

    // This does not, demonstrating that `'c: 'b` is not implied
    // (i.e. the implied bound is on the trait object lifetime only, and
    // not on the other parameters.)
    //let _: &'b [()] = c;

    // This does not as it requires `'c: 'b` and `'b: 'a`
    //f(t);
}
}

This is similar to how &'b &'a _ creates an implied 'a: 'b bound. It only applies to the trait object lifetime, and not the entirety of the dyn Trait (e.g. it does not apply to trait parameters).

The 'static case

We've already summarized the behavior of trait object lifetime elision when the trait itself has a 'static bound as part of our basic guidelines: the lifetime in this case is always 'static.

This applies even to

  • types with ambiguous (more than one) lifetime bounds
  • types with a single lifetime bound like &_
    • i.e. the trait object lifetime (which is 'static) becomes independent of the outer lifetime
  • situations where a non-'static bound does not override the &_ trait object lifetime default, as in some of the examples further below

This case applies even if there are multiple bounds and only one of them is 'static, in contrast with bounds considered ambiguous from the struct definition.

A single trait lifetime bound does not always apply

According to the reference, the default trait object lifetime for a trait with a single lifetime bound in the context of a generic struct with no lifetime bounds is always the lifetime in the trait's bound.

That's a mouthful, but the implication is that here:

#![allow(unused)]
fn main() {
trait Single<'a>: 'a {}
}

The elided lifetime of Box<dyn Single<'a>> is always 'a.

However, this is not actually the case:

#![allow(unused)]
fn main() {
trait Single<'a>: 'a {}
// The elided lifetime was `'static`, not `'a`, so this compiles
fn foo<'a>(s: Box<dyn Single<'a>>) {
    let s: Box<dyn Single<'a> + 'static> = s;
}
}
#![allow(unused)]
fn main() {
trait Single<'a>: 'a {}
// In this case it *is* `'a`, so compilation fails
fn bar<'a: 'a>(s: Box<dyn Single<'a>>) {
    let s: Box<dyn Single<'a> + 'static> = s;
}
}

When they apply, trait lifetime bounds override struct bounds

According to the reference, bounds on the trait never override bounds on the struct. But based on my testing, the opposite is true: when bounds on the trait apply, they always override the bounds on the struct.

The complicated part is figuring out when they apply.

For example, the following compiles, but according to the reference it should be ambiguous due to the multiple lifetime bounds on the struct. It does not compile without the lifetime bound on the trait; the bound on the trait is overriding the ambiguous bounds on the struct.

#![allow(unused)]
fn main() {
use core::marker::PhantomData;

// Remove `: 'a` to see the compile error
pub trait LifetimeTrait<'a>: 'a {}

pub struct Over<'a, T: 'a + 'static + ?Sized>(&'a T);

pub struct Invariant<T: ?Sized>(*mut PhantomData<T>);
unsafe impl<T: ?Sized> Sync for Invariant<T> {}

pub static OS: Invariant<Over<'_, dyn LifetimeTrait>> = Invariant(std::ptr::null_mut());
}

Further below are some examples where the trait bound overrides the &_ bounds as well, so it is not just ambiguous struct bounds which can be overridden by trait bounds.

Multiple trait bounds can be ambiguous or can apply

The following is considered ambiguous due to the multiple lifetime bounds on the trait.

#![allow(unused)]
fn main() {
trait Double<'a, 'b>: 'a + 'b {}
fn f<'a, 'b, T: Double<'a, 'b> + 'static>(t: T) {
    let bx: Box<dyn Double<'a, 'b>> = Box::new(t);

    // This version works:
    let bx: Box<dyn Double<'a, 'b> + 'static> = Box::new(t);
}
}

The current documentation is silent on this point, but a multiple-bound trait can still apply in such a way that it provides the default trait object lifetime.

#![allow(unused)]
fn main() {
pub trait Double<'a, 'b>: 'a + 'b {}

fn x1<'a: 'a, 'b>(bx: Box<dyn Double<'a, 'b>>) {
    // This fails (the lifetime is not `'static`)
    //let bx: Box<dyn Double<'a, 'b> + 'static> = bx;

    // This also fails (the lifetime is not `'b` nor `'a + 'b`)
    //let bx: Box<dyn Double<'a, 'b> + 'b> = bx;

    // But this succeeds and we can conclude the lifetime is `'a`
    let bx: Box<dyn Double<'a, 'b> + 'a> = bx;
}
}

There's a subtle point here: the elided trait object lifetime is 'a, but there's an implied : 'a + 'b bound on the trait object lifetime due to the trait bounds. Therefore the function signature has an implied 'a: 'b bound, similar to when you have a &'b &'a _ argument.

Trait bounds always apply in function bodies

Based on my testing, the default trait object lifetime for annotations of dyn Trait in function bodies is always the trait bound. And in fact, this bound even overrides the wildcard '_ lifetime annotation.

This is a surprising exception to the '_ annotation restoring "normal" lifetime elision behavior.

#![allow(unused)]
fn main() {
trait Single<'a>: 'a {}

fn baz<'long: 'a, 'a, T: 'long + Single<'a>>(s: T) {
    // This compiles with the assignment at the end:
    //let s: Box<dyn Single<'a> + 'long> = Box::new(s);

    // But none of these compile because `'a: 'long` does not hold:
    //let s: Box<dyn Single<'a>> = Box::new(s);
    //let s: Box<dyn Single<'_>> = Box::new(s);
    //let s: Box<dyn Single<'a> + '_> = Box::new(s);
    //let s: Box<dyn Single<'_> + '_> = Box::new(s);
    //let s: Box<dyn Single + '_> = Box::new(s);
    let s: Box<dyn Single> = Box::new(s);

    let s: Box<dyn Single<'_> + 'long> = s;
}
}

When and how to trait lifetime bounds apply?

Now that we've seen a number of examples, we can theorize when and how trait lifetime bounds apply. As the examples have already illustrated, there are very different rules for different contexts.

In function signatures

This appears to be the most complex and arbitrary context for trait object lifetime elision.

If you were paying close attention, you may have noticed that we occasionally had trivial bounds like 'a: 'a in the examples above, and that affected whether the trait bounds applied or not. A lifetime parameter of a function with no explicit bounds is known as a late-bound parameter, and whether or not a lifetime is late-bound influences when the trait bounds apply in function signatures. Parameters which are not late-bound are early-bound.

Let us call a lifetime parameter of a trait which is also a bound of the trait a "bounding parameter". My hypothesis on the behavior is as follows:

  • if any trait bound is 'static, the default lifetime is 'static
  • if any bounding parameter is explicitly 'static, the default lifetime is 'static
  • if exactly one bounding parameter is early-bound, the default lifetime is that lifetime
    • including if it is in multiple positions, such as dyn Double<'a, 'a>
  • if more than one bounding parameter is early-bound, the default lifetime is ambiguous
  • if no bounding parameters are early-bound, the default lifetime depends on the struct bounds (the same as they do for a trait without bounds)

Note that in any case, the implied bounds on the trait object lifetime that exist due to the trait bounds are still in effect.

The requirement that exactly one of the bounding parameters is early-bound or that any of them are 'static are syntactical requirements, rather than semantic ones. For example:

#![allow(unused)]
fn main() {
pub trait Double<'a, 'b>: 'a + 'b {}

// Semantically, `'a` and `'b` must be `'static`.  However the
// parameters were not explicitly `'static` and thus this
// trait object lifetime is considered ambiguous (even though,
// due to the implied bounds, it must be `'static` too).
fn foo<'a: 'static, 'b: 'static>(d: Box<dyn Double<'a, 'b>>) {}

// Semantically, `'a` and `'b` must be the same.  They are also
// early-bound parameters due to the bounds.  However the parameters
// are not syntatically the same lifetime and thus this trait
// object lifetime is considered ambiguous.
fn bar<'a: 'b, 'b: 'a>(d: &dyn Double<'a, 'b>) {}
}

But if you change either example to Double<'a, 'a>, then exactly one of the bounding parameters is early-bound, and they will compile:

#![allow(unused)]
fn main() {
pub trait Double<'a, 'b>: 'a + 'b {}
fn foo<'a: 'static, 'b: 'static>(d: Box<dyn Double<'a, 'a>>) {}
fn bar<'a: 'b, 'b: 'a>(d: &dyn Double<'a, 'a>) {}
}

Implicit bounds do not negate being late-bound

Note that when considering &dyn Trait there is always an implied bound between the outer reference's lifetime and the dyn Trait (in addition to the implied bound from the trait itself). However, these implied bounds are not enough to make the trait bound apply on their own. A lifetime can be late-bound even when there are implied bounds.

#![allow(unused)]
fn main() {
pub trait LifetimeTrait<'a>: 'a {}
impl LifetimeTrait<'_> for () {}

// All of these compile with the `fp` function below, indicating that
// the trait bound does in fact apply and results in a trait object
// lifetime independent of the reference lifetime
pub fn f<'a: 'a>(_: &dyn LifetimeTrait<'a>) {}
//pub fn f<'a: 'a>(_: &'_ dyn LifetimeTrait<'a>) {}
//pub fn f<'r, 'a: 'a>(_: &'r dyn LifetimeTrait<'a>) {}
//pub fn f<'r: 'r, 'a: 'a>(_: &'r dyn LifetimeTrait<'a>) {}
//pub fn f<'r, 'a: 'r + 'a>(_: &'r dyn LifetimeTrait<'a>) {}
//pub fn f<'r: 'r, 'a: 'r>(_: &'r dyn LifetimeTrait<'a>) {}

// However none of these compile with `fp`, indicating that the elided trait
// object lifetime is defaulting to the reference lifetime "per normal".
//pub fn f(_: &dyn LifetimeTrait) {}
//pub fn f(_: &'_ dyn LifetimeTrait) {}
//pub fn f<'r>(_: &'r dyn LifetimeTrait) {}
//pub fn f<'r: 'r>(_: &'r dyn LifetimeTrait) {}

//pub fn f(_: &dyn LifetimeTrait<'_>) {}
//pub fn f(_: &'_ dyn LifetimeTrait<'_>) {}
//pub fn f<'r>(_: &'r dyn LifetimeTrait<'_>) {}
//pub fn f<'r: 'r>(_: &'r dyn LifetimeTrait<'_>) {}

//pub fn f<'a>(_: &dyn LifetimeTrait<'a>) {}
//pub fn f<'a>(_: &'_ dyn LifetimeTrait<'a>) {}
//pub fn f<'r, 'a>(_: &'r dyn LifetimeTrait<'a>) {}
//pub fn f<'r: 'r, 'a>(_: &'r dyn LifetimeTrait<'a>) {}

// n.b. `'a` is invariant due to being a trait parameter
fn fp<'a>(t: &(dyn LifetimeTrait<'a> + 'a)) {
    f(t);
}
}

The above examples also demonstrate that when trait bounds apply, they do override non-ambiguous struct bounds (such as those of &_).

Implied bounds and default object bounds interact

The interaction between what the default object lifetime is for a given signature can interact in potentially surprising ways. Consider this example:

#![allow(unused)]
fn main() {
pub trait LifetimeTrait<'a>: 'a {}
// The implied bounds in `&'outer (dyn Lifetime<'param> + 'trait)` are:
// - `'param: 'outer` (validity of the reference)
// - `'trait: 'outer` (validity of the reference)
// - `'trait: 'param` (from the trait bound)
//
// And as the trait bound does not apply to the elided parameter in this
// case, we also have `'outer = 'trait` due to the "normal" default
// lifetime behavior of `&_`.  Adding that equality to the above bounds
// results in a requirement that *all three lifetimes are the same*.
//
// And thus this compiles:
pub fn g<'r, 'a>(d: &'r dyn LifetimeTrait<'a>) {
    let r: [&'r (); 1] = [&()];
    let a: [&'a (); 1] = [&()];
    let _: [&'a (); 1] = r;
    let _: [&'r (); 1] = a;
    let _: &'r (dyn LifetimeTrait<'r> + 'r) = d;
    let _: &'a (dyn LifetimeTrait<'a> + 'a) = d;
}
}

The results can be even more surprising with more complex bounds:

#![allow(unused)]
fn main() {
trait Double<'a, 'b>: 'a + 'b {}

fn h<'a, 'b, T>(bx: Box<dyn Double<'a, 'b>>, t: &'a T)
where
    &'a T: Send, // this makes `'a` early-bound
{
    // `bx` is `Box<dyn Double<'a, 'b> + 'a>` as per the rules above,
    // so this does not compile:
    //let _: Box<dyn Double<'a, 'b> + 'static> = bx;

    // However, the implied bounds still apply, which means:
    // - `'a: 'a + 'b`
    // - So `'a: 'b`
    //
    // Which is why this can compile even though that bound
    // is not declared anywhere!
    let t: &'b T = t;

    // The lifetimes are still not the same, so this fails
    let _: &'a T = t;
}
}

The only reason that 'a: 'b is an implied bound in the above example is the interaction between

  • the implied : 'a + 'b bound on the trait object lifetime
  • the default trait object lifetime being 'a
    • due to 'a being early-bound and 'b being late-bound

If 'b was also early-bound, the default trait object lifetime would be ambiguous. If 'a wasn't early-bound, the default trait object lifetime would be 'static and there would be no implied 'a: 'b bound.

The wildcard lifetime still introduces a fresh inference lifetime

Based on my testing, using '_ will behave like typical lifetime elision, introducing a fresh inference lifetime in input position, and following the function signature elision rules in output position.

Higher-ranked lifetimes are late-bound

Based on my testing, for<'a> dyn Trait... lifetimes act the same as late-bound lifetimes.

Function bodies

As mentioned above, trait object bounds always apply in function bodies, similar to function signatures where every lifetime is early-bound. This is true regardless of whether the lifetimes are early or late bound in the function signature.

#![allow(unused)]
fn main() {
trait Single<'a>: 'a {}

fn foo<'r, 'a>(bx: Box<dyn Single<'a> + 'static>, rf: &'r (dyn Single<'a> + 'static)) {
    // Here it is `'a`, and not `'static` nor inferred
    let bx: Box<dyn Single<'a>> = bx;
    // So this fails
    //let _: Box<dyn Single<'a> + 'static> = bx;

    // Here it is `'a`, and not the same as the reference lifetime nor inferred
    let a: &dyn Single<'a> = rf;
    // So this succeeds
    let _: &(dyn Single<'a> + 'a) = a;
    // And this fails
    //let _: &(dyn Single<'a> + 'static) = a;

    // Same behavior when the reference lifetime is explicit
    let a: &'r dyn Single<'a> = rf;
    let _: &'r (dyn Single<'a> + 'a) = a;
    //let _: &'r (dyn Single<'a> + 'static) = a;

    // This also fails, demonstrating that `'r` is not `'a`
    //let _: &'a &'r () = &&();
}
}

And unlike elsewhere, using '_ in place of complete trait object lifetime elision in the function body does not restore the normal lifetime elision behavior (which would be inferring the lifetime). All three of the examples above behave identically if '_ is used.

#![allow(unused)]
fn main() {
trait Single<'a>: 'a {}
fn foo<'r, 'a>(bx: Box<dyn Single<'a> + 'static>, rf: &'r (dyn Single<'a> + 'static)) {
    let bx: Box<dyn Single<'a> + '_> = bx;
    // Fails
    //let _: Box<dyn Single<'a> + 'static> = bx;

    let a: &(dyn Single<'a> + '_) = rf;
    let _: &(dyn Single<'a> + 'a) = a;
    // Fails
    //let _: &(dyn Single<'a> + 'static) = a;

    let a: &'r (dyn Single<'a> + '_) = rf;
    let _: &'r (dyn Single<'a> + 'a) = a;
    // Fails
    //let _: &'r (dyn Single<'a> + 'static) = a;
}
}

In combination with the behavior of function signatures, this can lead to some awkward situations.

#![allow(unused)]
fn main() {
trait Double<'a, 'b>: 'a + 'b {}

// Here in the signature, `'_` acts like "normal" and creates an
// independent lifetime for the trait object lifetime; let us call
// it `'c`.  Though independent, it is related due to the implied
// bounds: `'c: 'a + 'b`
fn foo<'a, 'b>(bx: Box<dyn Double<'a, 'b> + '_>) {
    // Here in the body, the default trait object lifetime is
    // considered ambiguous, and `'_` does not override this.
    //
    // Moreover, there is no way to name `'c` since it was
    // elided in the signature.  We could annotate this as
    // either `'a` or `'b`, but cannot "preserve" the full
    // lifetime unless we change the function signature to
    // give the lifetime a name.
    let bx: Box<dyn Double<'a, 'b> + '_> = bx;
}
}

Static contexts

In most static contexts, any elided lifetimes (not just trait object lifetimes) default to the 'static lifetime.

#![allow(unused)]
fn main() {
use core::marker::PhantomData;
trait Single<'a>: 'a + Send + Sync {}
trait Halfie<'a, 'b>: 'a + Send + Sync {}
trait Double<'a, 'b>: 'a + 'b + Send + Sync {}

static BS: PhantomData<Box<dyn Single<'_>>> = PhantomData;
static BH: PhantomData<Box<dyn Halfie<'_, '_>>> = PhantomData;
static BD: PhantomData<Box<dyn Double<'_, '_>>> = PhantomData;

static S_BS: PhantomData<Box<dyn Single<'static> + 'static>> = BS;
static S_BH: PhantomData<Box<dyn Halfie<'static, 'static> + 'static>> = BH;
static S_BD: PhantomData<Box<dyn Double<'static, 'static> + 'static>> = BD;

const CS: PhantomData<Box<dyn Single<'_>>> = PhantomData;
const CH: PhantomData<Box<dyn Halfie<'_, '_>>> = PhantomData;
const CD: PhantomData<Box<dyn Double<'_, '_>>> = PhantomData;

const S_CS: PhantomData<Box<dyn Single<'static> + 'static>> = CS;
const S_CH: PhantomData<Box<dyn Halfie<'static, 'static> + 'static>> = CH;
const S_CD: PhantomData<Box<dyn Double<'static, 'static> + 'static>> = CD;
}

However, from Rust 1.64 forward, associated consts were allowed to use general elided lifetimes and the wildcard lifetime (as opposed to only elided trait object lifetimes). This was an accidental stabilization which will probably be removed or modified.

In the meanwhile, elided lifetimes act like independent lifetime variables on the impl block. Those in turn act like early-bound lifetimes in function signatures.

#![allow(unused)]
fn main() {
use core::marker::PhantomData;
trait Single<'a>: 'a + Send + Sync {}
struct L<'l, 'm>(&'l str, &'m str);
impl<'a, 'b> L<'a, 'b> {
    const CS: PhantomData<Box<dyn Single<'a>>> = PhantomData;

    // Fails
    //const S_CS: PhantomData<Box<dyn Single<'a> + 'static>> = Self::CS;
    const S_CS: PhantomData<Box<dyn Single<'a> + 'a>> = Self::CS;
}
}

Elided lifetimes can be inferred to be 'static elsewhere...

#![allow(unused)]
fn main() {
use core::marker::PhantomData;
trait Single<'a>: 'a + Send + Sync {}
struct L<'l, 'm>(&'l str, &'m str);
impl<'a, 'b> L<'a, 'b> {
    const ECS: PhantomData<Box<dyn Single<'_>>> = PhantomData;
    const SCS: PhantomData<Box<dyn Single<'static>>> = PhantomData;

    const S_ECS: PhantomData<Box<dyn Single<'static> + 'static>> = Self::ECS;
    const S_SCS: PhantomData<Box<dyn Single<'static> + 'static>> = Self::SCS;
}
}

...however, it's really a free variable. Therefore, cases such as this are considered ambiguous:

#![allow(unused)]
fn main() {
use core::marker::PhantomData;
trait Double<'a, 'b>: 'a + 'b + Send + Sync {}
struct L<'l, 'm>(&'l str, &'m str);
impl<'a, 'b> L<'a, 'b> {
    const EBCD: PhantomData<Box<dyn Double<'a, '_>>> = PhantomData;
}
}

...and cases such this are considered to be a borrow check violation, as there are no outlives relationships between the anonymously introduced lifetime parameters:

#![allow(unused)]
fn main() {
use core::marker::PhantomData;
trait Single<'a>: 'a + Send + Sync {}
struct R<'l, 'm, 'r>(&'l str, &'m str, &'r ());
impl<'a, 'b, 'r> R<'a, 'b, 'r> where 'a: 'r, 'b: 'r {
    const ECS: PhantomData<&dyn Single<'_>> = PhantomData;
    const RECS: PhantomData<&'r dyn Single<'_>> = PhantomData;
}
}

(There is no implicit bound due to nesting the lifetimes because the nesting occurs in the body of the impl block and not the header.)

impl headers

Trait bounds always apply in impl headers.

#![allow(unused)]
fn main() {
trait Single<'a>: 'a {}
trait Halfie<'a, 'b>: 'a {}
trait Double<'a, 'b>: 'a + 'b {}

struct S<T>(T);

// The trait bounds apply
impl<'a> S<Box<dyn Single<'a>>> { fn f01() {} } // 'a (not 'static)
impl<'a, 'r> S<&'r dyn Single<'a>> { fn f02() {} } // 'a (not 'r)
impl<'a, 'b> S<Box<dyn Halfie<'a, 'b>>> { fn f03() {} } // 'a (not 'static)
impl<'a, 'b, 'r> S<&'r dyn Halfie<'a, 'b>> { fn f04() {} } // 'a (not 'r)
// Ambiguous (uncomment for error)
// impl<'a, 'b> S<Box<dyn Double<'a, 'b>>> { fn f05() {} }
// impl<'a, 'b, 'r> S<&'r dyn Double<'a, 'b>> { fn f05() {} }

// Try `+ 'static` or `+ 'r` for errors
fn f<'a, 'b, 'r>(_: &'r &'a str, _: &'r &'b str) {
    S::<Box<dyn Single<'a> + 'a>>::f01();
    S::<&'r (dyn Single<'a> + 'a)>::f02();
    S::<Box<dyn Halfie<'a, 'b> + 'a>>::f03();
    S::<&'r (dyn Halfie<'a, 'b> + 'a)>::f04();
}
}

As in function signatures, but unlike function bodies, the wildcard lifetime '_ acts like normal elision (introducing a new anonymous lifetime variable).

#![allow(unused)]
fn main() {
trait Single<'a>: 'a {}
trait Halfie<'a, 'b>: 'a {}
trait Double<'a, 'b>: 'a + 'b {}
struct S<T>(T);
// The wildcard lifetime `'_` introduces an independent lifetime
// (covering all cases including `'static`) as per normal
impl<'a> S<Box<dyn Single<'a> + '_>> { fn f26() {} }
impl<'a, 'r> S<&'r (dyn Single<'a> + '_)> { fn f27() {} }
impl<'a, 'b> S<Box<dyn Halfie<'a, 'b> + '_>> { fn f28() {} }
impl<'a, 'b, 'r> S<&'r (dyn Halfie<'a, 'b> + '_)> { fn f29() {} }
impl<'a, 'b> S<Box<dyn Double<'a, 'b> + '_>> { fn f30() {} }
impl<'a, 'b, 'r> S<&'r (dyn Double<'a, 'b> + '_)> { fn f31() {} }

fn f<'a, 'b, 'r>(_: &'r &'a str, _: &'r &'b str) {
    S::<Box<dyn Single<'a> + 'static>>::f26();
    S::<&'r (dyn Single<'a> + 'static)>::f27();
    S::<Box<dyn Halfie<'a, 'b> + 'static>>::f28();
    S::<&'r (dyn Halfie<'a, 'b> + 'static)>::f29();
    S::<Box<dyn Double<'a, 'b> + 'static>>::f30();
    S::<&'r (dyn Double<'a, 'b> + 'static)>::f31();
}
}

Associated types

Similar to impl headers, trait bounds always apply to associated types.

#![allow(unused)]
fn main() {
use core::marker::PhantomData;
trait Single<'a>: 'a {}
trait Halfie<'a, 'b>: 'a {}
trait Double<'a, 'b>: 'a + 'b {}

trait Assoc {
    type A01: ?Sized + Default;
    type A02: ?Sized + Default;
    //type A03: ?Sized + Default;
    type A04: ?Sized + Default;
    type A05: ?Sized + Default;
    //type A06: ?Sized + Default;
}

impl<'r, 'a, 'b> Assoc for (&'r &'a (), &'r &'b ()) {
    // '_ is not allowed here
    // & /* elided */ is not allowed here
    type A01 = PhantomData<Box<dyn Single<'a>>>;
    type A02 = PhantomData<Box<dyn Halfie<'a, 'b>>>;
    // ambiguous
    // type A03 = PhantomData<Box<dyn Double<'a, 'b>>>;
    type A04 = PhantomData<&'r dyn Single<'a>>;
    type A05 = PhantomData<&'r dyn Halfie<'a, 'b>>;
    // ambiguous
    // type A06 = PhantomData<&'r dyn Double<'a, 'b>>;
}

fn f<'r, 'a: 'r, 'b: 'r>() {
    // 'a (not `'static`, `'r`, `'b`)
    let _: PhantomData<Box<dyn Single<'a> + 'a>> = <(&'r &'a (), &'r &'b ()) as Assoc>::A01::default();
    let _: PhantomData<Box<dyn Halfie<'a, 'b> + 'a>> = <(&'r &'a (), &'r &'b ()) as Assoc>::A02::default();
    let _: PhantomData<&'r (dyn Single<'a> + 'a)> = <(&'r &'a (), &'r &'b ()) as Assoc>::A04::default();
    let _: PhantomData<&'r (dyn Halfie<'a, 'b> + 'a)> = <(&'r &'a (), &'r &'b ()) as Assoc>::A05::default();
}
}

Note: I have not performed extensive tests with GATs or associated types which themselves have lifetime bounds in combination with bounded traits.