RPITIT "captures lifetime that does not appear in bounds" #128752
Description
I tried this code: playground
trait MyTrait {
fn foo<'a, 'b>(&'a self, x: &'b i32) -> impl Future<Output = i32>;
}
trait ErasedMyTrait {
fn foo<'life0, 'life1, 'dynosaur>(&'life0 self, x: &'life1 i32)
-> Pin<Box<dyn Future<Output = i32> + 'dynosaur>>
where
'life0: 'dynosaur,
'life1: 'dynosaur;
}
struct DynMyTrait<T: ErasedMyTrait> {
ptr: T,
}
impl<T: ErasedMyTrait> MyTrait for DynMyTrait<T> {
fn foo<'a, 'b>(&'a self, x: &'b i32) -> impl Future<Output = i32> {
self.ptr.foo(x)
}
}
Diagnostics
I got a nonsensical error message:
error[E0700]: hidden type for `impl Future<Output = i32>` captures lifetime that does not appear in bounds
--> src/lib.rs:22:9
|
21 | fn foo<'a, 'b>(&'a self, x: &'b i32) -> impl Future<Output = i32> {
| -- ------------------------- opaque type defined here
| |
| hidden type `Pin<Box<(dyn Future<Output = i32> + 'b)>>` captures the lifetime `'b` as defined here
22 | self.ptr.foo(x)
| ^^^^^^^^^^^^^^^
|
help: to declare that `impl Future<Output = i32>` captures `'b`, you can add an explicit `'b` lifetime bound
|
21 | fn foo<'a, 'b>(&'a self, x: &'b i32) -> impl Future<Output = i32> + 'b {
| ++++
This is an RPITIT so the lifetime should not have to appear in the bounds to be captured. As you might expect, adding the + 'b
leads to a different error, and using the Captures
trick does not work either (in fact, it is quite a mess of surprising errors: playground).
Should this compile?
Diagnostics aside, it's understandable that this wouldn't compile, because the compiler doesn't know what to do with the extra 'dynosaur
lifetime in ErasedMyTrait::foo
. It would have to generate an intersection lifetime to get the full return type of that method (the 'dynosaur
in Pin<Box<dyn Future<Output = i32> + 'dynosaur>>
.
Adding a lifetime that corresponds to 'dynosaur
to the original trait does the trick:
trait MyTrait {
fn foo<'a, 'b, 'd>(&'a self, x: &'b i32) -> impl Future<Output = i32>
where 'a: 'd, 'b: 'd;
}
As I alluded to above, I don't think have we to know which lifetime 'dynosaur
corresponds to to know that the original program is sound. Representing it as the intersection of 'a
and 'b
should be enough to see that our Pin<Box<dyn Future>>
satisfies -> impl Future
in the signature of the method we're implementing.
Through some magic it isn't necessary if we use async fn directly; this impl compiles fine:
impl<T: ErasedMyTrait> MyTrait for DynMyTrait<T> {
async fn foo<'a, 'b>(&'a self, x: &'b i32) -> i32 {
self.ptr.foo(x).await
}
}
I'm pretty much expecting to hear that this is too hard to support now, but the async fn chicanery gives me hope that it can be supported sooner.
Meta
rustc version:
1.82.0-nightly
(2024-08-05 e57f3090aec33cdbf660)
Activity