Captures and + '_
aren't exactly the same thing. But if you have worked with -> impl Trait
outside of traits, I definitely understand why you have that intuition. It is the case that -> impl Trait
in traits implicitly captures all generics, including all lifetimes.
In contrast, -> impl Trait
outside of traits does not implicitly capture lifetime generics unless you add something like + '_
. Until the next edition, where they'll act like -> impl Trait
in traits.
-> impl Trait
implicitly defines an opaque type, or more properly, an opaque type constructor (parameterized by the generics it captures).
"Capturing" means that the opaque type can be defined in terms of the captured parameter in some way, like the parameters on a generic associated type. Like with a GAT, it doesn't have to use the parameters, but it is allowed to. Also like a GAT, any bounds also have to be met. Unlike a GAT, opaque types cannot be normalized, so outside consumers must always act like an opaque type uses all of its parameters.
+ '_
is a bound, not a capture. However, outside of traits, on the current edition, -> impl Trait
doesn't capture lifetimes by default. But they are captured if a lifetime is mentioned in a bound.
That's a lot of words, so let's see some code:
trait Example {
fn some_trait_method<T>(&self) -> impl Trait + 'static;
}
trait Example {
// Every `+` separated thing after `-> impl` became a bound
opaque type StmOut<'a, T>: Trait + 'static;
fn some_trait_method<T>(&self) -> Self::StmOut<'_, T>;
}
Without the + 'static
, the lifetime is still captured.
trait Example {
// fn some_trait_method<T>(&self) -> impl Trait;
opaque type StmOut<'a, T>: Trait;
fn some_trait_method<T>(&self) -> Self::StmOut<'_, T>;
}
But that's not that same as "an implicit + '_
":
trait Example {
// fn some_trait_method<T>(&self) -> impl Trait + '_;
// fn some_trait_method<'s, T>(&'s self) -> impl Trait + 's;
// vvvv ^^^^
opaque type StmOut<'a, T>: Trait + 'a;
fn some_trait_method<T>(&self) -> Self::StmOut<'_, T>;
}
Adding + '_
resulted in a new bound on the opaque type, which in turn requires T: 'a
in the opaque type. That wasn't required when + '_
wasn't present. And often this isn't what you want, in which case implicit capturing is superior, assuming you do need to capture the lifetime.
For that reason, and consistency, -> impl Trait
outside of traits will also capture all generics in the next edition. And we'll get precise capturing for backwards compatibility.
There was a plan to implicitly drop the lifetime capturing sometimes if a + 'static
implied a lifetime parameter couldn't be used, but it didn't pan out apparently; hence precise capturing.
Here's another (contrived) example where a bound requires a lifetime parameter to be 'static
...
struct Foo;
impl Trait for &'static Foo {}
impl Foo {
fn example<'s>(&'s self) where &'s Self: Trait {}
}
...but that doesn't mean example
isn't parameterized by a lifetime. That's sort of like what's going on in your OP.