You are not returning anything in the implementation. I think the error comes from rustc not being able to infer the lifetime of the returned object.
If you include the impl Trait by this:
impl Foo for MyFoo {
type Bar<'a> = ();
fn foo(&self, _: Self::Bar<'_>) -> impl Debug {}
}
It compiles without problem. Probably someone can explain it in more detail, but I suspect the compiler is actually including the self lifetime into the impl return, like:
fn foo<'s>(&'s self, bar: Self::Bar<'_>) -> impl Debug + use<'s>;
So your implementation does not have the same lifetime as the declaration.
I'm sure you have already found out, but you can get it to work by only using one lifetime parameter which in turn requires a bound like Self: 'a for example:
trait Foo {
type Bar<'a>
where
Self: 'a;
fn foo<'a>(&'a self, bar: Self::Bar<'a>) -> impl Debug;
}
struct MyFoo;
impl Foo for MyFoo {
type Bar<'a>
= ()
where
Self: 'a;
fn foo<'a>(&'a self, _: Self::Bar<'a>) {}
}
Of course that changes the definition of Foo though.
This part of the RFC is the closest I could find that goes over how "awkward" desguaring becomes when multiple lifetimes are involved which is the case for your example since without elision the trait def is:
trait Foo {
type Bar<'a>;
fn foo<'a, 'b>(&'a self, bar: Self::Bar<'b>) -> impl Debug;
}
...but that syntax is behind an unstable feature still.
I played around some more and I believe I've figured it out. It still deserves a diagnostic issue at a minimum.
When a method lifetime parameter participates in an explicit bound, it becomes "early bound" -- the method item type is parameterized by said lifetime, and you can turbo-fish it. But when a lifetime does not participate in any explicit bound,[1] it is "late bound" -- the lifetime appears in the notional implementation of the Fn traits, but not in the type of the method item itself, and it is not turbofishable. This is what we're used to happening with elided lifetimes.
The compiler lets you change the number of late bound lifetimes you put on a method in an implementation, but not the number of early bound lifetimes -- because in a generic context, you can still turbofish the early bound lifetimes, for example.
And here's what's happening: the -> use<..> are making the lifetimes early bound (even when it's elided). But in the refined signature, they were late bound. So here's another fix for the OP:
impl Foo for MyFoo {
type Bar<'a> = ();
// vvvvvv makes `'a` an early-bound lifetime
fn foo<'a:'a>(&self, _: Self::Bar<'a>) {}
// This also works
fn foo<'a:'a>(&self, _: ()) {}
}
As far as I understand, capturing all the lifetimes is the expected behavior until the use<...> syntax gets introduced (the compiler even warns you about it in stable), similar to how currently every GAT has the Self: 'a implicit bound.
Correct for -> impl Trait in traits, and also outside of traits in edition 2024. We have precise capturing (use<>) outside of traits already, but not in traits yet.
I'm not sure why you thought I needed clarification on that in the context of my post though.
I just wanted to be sure I understood it correctly. I've been experimenting with using more advanced generics in Rust, and I find hard to understand the "proper" rules, since the documentation is sparse at the moment.
The early/late is intentional, because generic associated types don't constrain their lifetime parameters (since you don't have to use the parameter in the definition).[1]
As that's a soundness consideration, probably the only "fix" to the "bugs" is if trait implementations automatically inherit early-boundedness from the trait definition, instead of being implied by the signature... which is also kinda bleh.
Or some much wider change to early-vs-late bounds more generally, but that seems distant-future to me.