Fun with `Trait where &Self: Bound` and `dyn Trait`

I don't know that this is a tutorial that will actually help anybody, but here are some things I played with due to another thread...


Setup: When you have a trait Trait where Self: Bound ..., this is the same as a supertrait (trait Trait: Bound), and dyn Trait: Bound will be implemented by the compiler automatically if possible. However, if the bound isn't on Self -- say it's on &Self -- you can end up with fun situations like object safe traits where dyn Trait exists but doesn't implement Trait.

Forging on ahead to make dyn Trait: Trait probably isn't the best approach at this point, but let's say we wanted to try anyway. Is it possible?

Let's look at a base example:

// Part 1: The general setup
fn f() {
    trait Marker<'a> {}

    trait Trait where for<'a> &'a Self: Marker<'a> {
        fn usable(&self) {}
    }

    impl<'a> Marker<'a> for &'a () {}
    impl Trait for () {}

    // `(): Trait`
    let u = ();
    u.usable();

    // `dyn Trait` exists and `()` can coerce to it...
    let t: &dyn Trait = &();
    // But `dyn Trait: !Trait`
    t.usable();
    // error[E0277]: the trait bound `for<'a> &'a dyn Trait: Marker<'a>` is not satisfied
}

We made a dyn Trait: !Trait. Fun! So the next question is, can we make this work anyway?


Let's follow the implied hint of the error and supply the missing implementation.
// Part 2: Let's implement what the error said we were missing
fn g() {
    trait Marker<'a> {}
    trait Trait where for<'a> &'a Self: Marker<'a> { fn usable(&self) {} }
    impl<'a> Marker<'a> for &'a () {}
    impl Trait for () {}

    let u = ();
    u.usable();
    let t: &dyn Trait = &();

    /*** New ***/
    impl<'a> Marker<'a> for &'a (dyn Trait /* + 'a */) {}

    // Doesn't work though...
    t.usable();
    //   |       ^^^^^^ implementation of `g::Marker` is not general enough
    //   |
    //   = note: `g::Marker<'0>` would have to be implemented for the type `&'0 dyn g::Trait`, for any lifetime `'0`...
    //   = note: ...but `g::Marker<'_>` is actually implemented for the type `&'1 (dyn g::Trait + '1)`, for some specific lifetime `'1`
}

It didn't work :frowning: This new error isn't quite right, either, because if you un-apply elision to the notes:

  • It's talking about missing &'0 dyn Trait + '0, but that can't be the actual requirement. We did implement that! And this is also what the note in Part 1 said!
  • It's saying we implemented for a specific '1, but that's also not true.

Inaccurate or no, the second note is the real clue, and the rest is actually just a shortcoming in the error reporting. dyn Trait lifetime elision doesn't penetrate aliases like &'a Self, and Self = dyn Trait, which is short for dyn Trait + 'static. So what we actually need to satisfy for the trait bounds is &'a (dyn Trait + 'static): Marker<'a>.


OK, let's try it.
// Part 3: What the where clause is really saying
fn h() {
    trait Marker<'a> {}
    trait Trait where for<'a> &'a Self: Marker<'a> { fn usable(&self) {} }
    impl<'a> Marker<'a> for &'a () {}
    impl Trait for () {}

    let u = ();
    u.usable();
    let t: &dyn Trait = &();

    /*** Altered: `+ 'static` instead of `+ 'a` ***/
    impl<'a> Marker<'a> for &'a (dyn Trait + 'static) {}

    // Works now
    t.usable();
}

Huzzah! It works. If &'a dyn (Trait + 'static) is good enough, we're done.


Side note: You get a variation on the misleading error when associated types are involved.
// Part 4: A variation on Part 2 using associated types
fn i() {
    trait Trait where for<'a> &'a Self: IntoIterator<Item=&'a ()> { fn usable(&self) {} }
    impl Trait for Vec<()> {}

    let v = vec![()];
    v.usable();
    let t: &dyn Trait = &v;
    
    impl<'a> IntoIterator for &'a dyn Trait {
        type Item = &'a ();
        type IntoIter = std::slice::Iter<'a, ()>;
        fn into_iter(self) -> Self::IntoIter {
            (&[]).into_iter()
        }
    }

    // Doesn't work for the same reaons...
    t.usable();
    //    |       ^^^^^^ the trait `for<'a> IntoIterator` is not implemented for `&'a dyn i::Trait`
    //    |
    //    = help: the following implementations were found:
    //              <&'a (dyn i::Trait + 'a) as IntoIterator>
}

This error is still incorrect (the ^^^ part), but the help about what it did find is an arguably better hint at what is really missing.


And before moving on...

Do other aliases work the same as `Self`?
// Part 5: `type` aliases items work like `Self`
fn j() {
    trait Marker<'a> {}
    trait Trait where for<'a> &'a Self: Marker<'a> { fn usable(&self) {} }
    impl<'a> Marker<'a> for &'a () {}
    impl Trait for () {}

    let u = ();
    u.usable();
    let t: &dyn Trait = &();

    type DynTrait = dyn Trait;
    impl<'a> Marker<'a> for &'a DynTrait {}

    // Yep, this works.  We implemented for dyn Trait + 'static
    t.usable();
}

Yes, they do. And seeing the code written out this way, it's pretty easy to see it as reasonable. The alias would have to have a lifetime parameter to be able to refer to in order to create a &'a (Trait + 'a) later. And Self doesn't have a lifetime parameter.


Alright, but what if we actually wanted our trait bound to be on &'a (dyn Trait + 'a), but still also wanted the bound to be on &'a Self for non-dyn implementers of the trait? Can we do that?

Well, almost.
// Part 6: Working around the alias
fn k() {
    // ✨ indirection ✨
    trait Helper<'a> {
        type Target: Marker<'a>;
    }

    // `T: Sized` (implicitly)
    impl<'a, T: 'a> Helper<'a> for &'a T where &'a T: Marker<'a> {
        type Target = &'a T;
    }

    // So this doesn't overlap
    impl<'a> Helper<'a> for &'a (dyn Trait + 'static) {
        type Target = &'a (dyn Trait + 'a);
    }

    // We rely on the helper which will point us to the actual `Marker<'a>`
    trait Marker<'a> {}
    trait Trait where for<'a> &'a Self: Helper<'a> { fn usable(&self) {} }
    
    impl<'a> Marker<'a> for &'a () {}
    impl Trait for () {}

    let u = ();
    u.usable();
    let t: &dyn Trait = &();

    // Works!
    impl<'a> Marker<'a> for &'a dyn Trait {}
    t.usable();
    
    // But sadly you'll need something like this for every
    // `DST where &DST: Marker<'_>`
    impl<'a> Marker<'a> for &'a str {}
    impl<'a> Helper<'a> for &'a str {
        type Target = &'a str;
    }
    impl Trait for str {}
    "".usable();
}

There's no such thing as a !dyn bound, so we have to approximate it with a (implicit) Sized bound instead. So this isn't quite what we were aiming for -- we need to add more Helper implementations for every non-Sized Marker implementer, like str in the example. ([T] could done generically at least.)

But if this is acceptable, you can have the bound apply to &'a (dyn Trait + 'a) instead.

(Is it useful though? Probably better to try and refactor in such a way that supertraits can meet your needs instead.)


Playground for everything.

5 Likes

:raised_hands: :clap: :raised_hands: :clap: :raised_hands: :clap: :raised_hands: :clap: :raised_hands: :clap: :raised_hands: :clap: :raised_hands: :clap: :raised_hands:

Thank you! Very appreciated! This night I will dive into!
It's a pity that I can give you only one like! :laughing:

Ahhhh, be warned this is more "poking at the boundaries" then "intro to this feature" or even "thing you would ever want to use" :sweat_smile: . dyn Trait lifetime bounds are pretty complicated / often obscure. The main parts I probably should have called out above are:

  • &'a dyn Trait is short for &'a (dyn Trait + 'a)
  • But outside of contexts like that, dyn Trait is short for (dyn Trait + 'static)

And the one-liner summary of the post is

  • &'a AliasForDynTrait is short for &'a (dyn Trait + 'static) as the lifetime elision cannot penetrate the alias

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.