error[E0700]: hidden type for `impl X` captures lifetime that does not appear in bounds
--> src/main.rs:10:5
|
9 | fn map_x(x: &impl X) -> impl X {
| ------- ------ opaque type defined here
| |
| hidden type `impl X` captures the anonymous lifetime defined here
10 | x.map()
| ^^^^^^^
|
help: to declare that `impl X` captures `'_`, you can add an explicit `'_` lifetime bound
|
9 | fn map_x(x: &impl X) -> impl X + '_ {
From my understanding, since X derives from 'static, wouldn't it be impossible for any implementation of the map function in X to return a new X that borrows from the original one, and hence the + '_ would be extraneous?
This is an accurate statement, but the compiler isn't using this information. It potentially could, but RPIT in traits always captures all input lifetimes, i.e. the trait you've written is equivalent to
Yes, this means lifetime capture for RPIT is different between trait and non-trait fn. The lang team chose to make RPIT in trait always capture because they believe this is more useful behavior for the sugar, the behavior of -> impl Trait + 'a and -> impl Trait + Captures<'a> is subtly different, and the lifetime capture behavior for non-trait RPIT is changing to match in-trait RPIT in the 2024 edition (meaning the OP example will compile as-is).
To explain one consequence of @CAD97's explanations: You can make map_x work by explicitly capturing the lifetime in question. The approach that works most generally would be to use a trait like
trait Captures<'a> {}
impl<T: ?Sized> Captures<'_> for T {}
In this particular case, with only a single lifetime to be captured and a + 'static supertrait bound already in place, it turns out using + '_ is pretty much equivalent, too (which would look like this); but using Captures<'a> (possible multiple times, Captures<'x> + Captures<'y> + ...) is the most general approach.
Unfortunately; whereas as demonstrated above you can rewrite the freestanding function's signature to match the trait method and make the code work; there is no way (yet) to go the other way, and rewrite the signature inside of the trait in a way that the lifetime isn't captured (whilst still allowing you not to have to name the concrete return type). Even though if this was possible, this would be the preferred solution in this case, where due to the + 'static lifetime constraints, really capturing the lifetime is entirely pointless (and not really possible), anyways.