Earlier today I rewrote a piece of old Rust code just to be confused by a compile error that did not exist in the old version of the code.
I've reduced it to the following minimal example. Basically, I want to return an impl trait from a method. In the old code this was the return type for a trait method (Factory::simple_trait() in the example). And this compiles without any errors.
In the updated code I wanted to get rid of the trait and moved the method to be directly on the struct impl (SimpleFactory::create_simple_trait() in the example). But this fails with the following compile error:
hidden type for `impl SimpleTrait` captures lifetime that does not appear in bounds
So I'm confused since the implementation code for Factory::simple_trait() and SimpleFactory::create_simple_trait() is exactly the same. The one is just an impl trait for struct while the other is an impl struct.
That raises the question: how are these two different? I suspect the trait version is doing something special with the reference to &self but am not sure what it is.
In the meanwhile, you can use the "Captures trick" from the other thread, or even the + '_ hint/suggestion (as in this case[1] there's no other generics that "inherit" the lifetime bound in a troublesome way).
Rust does a lot of lifetime elision so that your code isn't cluttered with lifetime annotations, but the elision rules are chosen to either be very common patterns, or to ensure that you're more likely to get an error than to get something that works by mistake.
Writing out the relevant code without lifetime elision gets you:
And now you have a problem - your return from self.simple_trait.get_or_init has a lifetime bound of 'this, but you need it to be 'static because that's the assumed elided lifetime.
The fix is to tell Rust that we want it to deduce the lifetime, rather than assume 'static as per the elision rules:
Lifetime elision in the form of -> impl Trait does not use bounds. That is, there's no desugaring or notional desugaring that corresponds to + '_ or + '_some_lifetime. And you generally don't want them to, as discussed in the other thread and its links.
The desugaring can be described in terms of precise capturing, which is another unstable feature. It would be something like this.[1]
trait Factory {
// Note that this does not require the return outlives `'s`
fn simple_trait<'s>(&'s self) -> impl use<'s, Self> SimpleTrait;
}
impl Factory for SimpleFactory {
fn simple_trait<'s>(&'s self) -> impl use<'s> SimpleTrait {
self.simple_trait.get_or_init(|| SimpleStruct)
}
}
// n.b. edition 2021 with precise capturing
impl SimpleFactory {
pub fn create_simple_trait(&self) -> impl use<> SimpleTrait {
self.simple_trait.get_or_init(|| SimpleStruct)
}
}
Until precise capturing, there's no desugaring to surface level Rust code, since there's no way to make GATs unnameable or to name closure types in your return type, etc. That said, the notional desugaring is somewhat close to...
trait Factory {
// Note that this does not require `UnnameableGat<'a>: 'a`
type UnnameableGat<'a>: SimpleTrait;
fn simple_trait<'s>(&'s self) -> UnnameableGat<'s>;
}
impl Factory for SimpleFactory {
// type UnanameableGat<'a> = impl SimpleTrait; // 'a is in scope
type UnnameableGat<'a> = CompilerProvidedBasedOnMethodBody<'a>;
fn simple_trait<'s>(&'s self) -> UnnameableGat<'s> {
self.simple_trait.get_or_init(|| SimpleStruct)
}
}
// n.b. edition 2021
// type SimpleFactoryCreate = impl Trait; // No lifetime in scope
type SimpleFactoryCreate = CompilerProvidedBasedOnMethodBody;
impl SimpleFactory {
pub fn create_simple_trait(&self) -> SimpleFactoryCreate {
self.simple_trait.get_or_init(|| SimpleStruct)
}
}
But the GAT/type aliases are unnameable, invariant in their parameters, and do not normalize (yet still leak auto traits).
As said before it probably doesn't matter for the OP code (+ '_ is probably fine given the concrete type), but don't get the idea that elision adds + '_ bounds. Those are something different than "capturing". If you need to adjust elided capturing in the future, precise capturing (use<..>) will be the way.
There may be syntactical differences from whatever is actually stabilized. ↩︎
Thanks a million for all the links!! Especially since I'm trying to understand the internals of Rust with this rather than just applying a quick fix to my problem.