Repository navigation
Description
I tried this code:
trait Original {
fn f() -> impl Fn();
}
// provide a type-erased helper trait for Original
trait Erased {
fn f(&self) -> Box<dyn Fn()>;
}
impl<T: Original> Erased for T {
fn f(&self) -> Box<dyn Fn()> {
Box::new(<T as Original>::f())
}
}I expected the code to compile successfully, but it failed on Rust 1.75.0 with the following error:
error[E0310]: the associated type `<T as Original>::{opaque#0}` may not live long enough
--> src/lib.rs:11:9
|
11 | Box::new(<T as Original>::f())
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| |
| the associated type `<T as Original>::{opaque#0}` must be valid for the static lifetime...
| ...so that the type `impl Fn()` will meet its required lifetime bounds
|
help: consider adding an explicit lifetime bound
|
10 | fn f(&self) -> Box<dyn Fn()> where <T as Original>::{opaque#0}: 'static {
| ++++++++++++++++++++++++++++++++++++++++++
For more information about this error, try `rustc --explain E0310`.
error: could not compile `playground` (lib) due to previous error
I understand that Box<dyn Fn()> is sugar for Box<dyn Fn() + 'static>, but in this case the impl Fn() return is 'static. It has to be, because Original::f() takes no arguments, so the value it returns cannot reference any non-static lifetime.
The workaround is to change the return type of Erased::f() from Box<dyn Fn()> to Box<dyn Fn() + '_>, which is allows the code to compile. (Playground.) (Another workaround is to change the definition of Original::f() to return impl Fn() + 'static, but in the original code Original is provided by a different crate and cannot be modified.)
A second, and minor issue is that the compiler's suggestion is not actionable because the opaque type returned from Original::f() cannot be named.
Activity
QuineDot commentedon Jan 21, 2024
It's pretty silly, but the returned closure doesn't have to be
'static(playground).hniksic commentedon Jan 21, 2024
@QuineDot Thanks for the interesting example. I think it's consistent with what was reported in the ticket, only provides the flip side of the coin, an example that compiles with the current behavior, but would no longer compiled if it were to change.
In other words, this code compiles, and I wouldn't expect it to:
It compiles because the lifetime bounds from the type that implements the trait are apparently baked into the return type, even when the method has no arguments. That seems wrong - I'd argue that, to get such behavior, one should be explicit about the lifetime carried around by the trait, as in:
The current behavior for methods that don't accept
&selfdoesn't really particularly useful, but does provide an example that would no longer compile if the original issue were fixed.QuineDot commentedon Jan 22, 2024
+ 'ais not always the answer either. Adding it to the trait also makes it invariant.Anyway, let me present a less silly example for the more general pattern. This implementation exists:
And this works for any sized
T, including those which are not'static; therefore, it cannot be changed toMoreover, this implementation is useful for things like this.
A rule that a return value couldn't capture a lifetime from the implementing type only would require removing the
Defaultimplementation (and the one for&[T]and any similar trait/implementation combos), which is too breaking to be tenable.Account for RPITIT in E0310 explicit lifetime constraint suggestion
Account for RPITIT in E0310 explicit lifetime constraint suggestion
Rollup merge of rust-lang#121435 - estebank:rpitit-static-119773, r=c…
Unrolled build for rust-lang#121435