This odd bit of code emits an error from do_the_thing()
saying T
does not live long enough:
use std::future::Future;
trait Connection: Send {
type ExecFut<'a>: Future<Output = ()> + Send
where
Self: 'a;
fn transaction<F>(&mut self, _f: F)
where
F: for<'a> FnOnce(&'a mut Self) -> Box<dyn Future<Output = ()> + Send + 'a> + Send,
{
unimplemented!()
}
fn execute<'a>(&'a mut self) -> Self::ExecFut<'a> {
unimplemented!()
}
}
fn do_the_thing<T: Connection>(connection: &mut T) {
connection.transaction(|conn| {
Box::new(async move {
conn.execute().await;
})
});
}
error[E0310]: the parameter type `T` may not live long enough
--> src/main.rs:22:9
|
22 | / Box::new(async move {
23 | | conn.execute().await;
24 | | })
| |__________^ ...so that the type `T` will meet its required lifetime bounds
|
help: consider adding an explicit lifetime bound...
|
20 | fn do_the_thing<T: Connection + 'static>(connection: &mut T) {
| +++++++++
See it on the playground.
As far as I can tell, there should be no implied bound that 'static
is required. Pretty much everything is constrained by 'a
and any lifetimes in T
should always outlive it regardless. Though obviously there might be something I'm missing.
I've also found a few "solutions" that aren't helpful:
'static
as the compiler suggests is not reasonable. It can be worked around but there are real Connection
s with non-'static
lifetimes that I would like to use.Send
bound on the dyn Future
makes it pass compilation. This makes zero sense to me.Connection
that is not 'static
it compiles as well, seemingly contradicting the compiler error.The above is not real code; the original motivation stems from using AsyncConnection
from the diesel-async crate and similar code structure. However, I hope it is representative of the core issue and that by understanding the problem and potential solutions here, they can be adapted.
From further tinkering and research, I found this issue - GATs: Decide whether to have defaults for where Self: 'a
- in regard to the where
clause on type ExecFut<'a>
. From the comments in the issue, it seems others have had similar errors from having this bound enforced.
There's a workaround provided from the issue, which is to split your trait into two - one for the associated types and another for the methods so that the where Self: 'a
clause can be omitted:
This breaks my code. Workaround?
First, if any code breaks from adding the required bounds, we really want feedback. Second, the workaround is to move the GAT into a super trait. Using the example above, our new code would look like:
trait IterableSuper { type Item<'x>; } trait Iterable: IterableSuper { fn iter<'a>(&'a self) -> Self::Item<'a>; }
Doing that on my example makes it compile but it does start to get messy in my target use-case.
Another workaround that I've seen mentioned was to introduce a dummy function that uses the associated type but not in conjunction with self
. This allows you to omit the where Self: 'a
clause as well, and thus the code compiles:
trait Connection: Send {
type ExecFut<'a>: Future<Output = ()> + Send;
// ^^^^^^^^^^^^^^^^ does not need `where Self: 'a`
fn transaction<F>(&mut self, _f: F)
where
F: for<'a> FnOnce(&'a mut Self) -> Box<dyn Future<Output = ()> + Send + 'a> + Send,
{
unimplemented!()
}
fn execute<'a>(&'a mut self) -> Self::ExecFut<'a> {
unimplemented!()
}
fn _disable_lint_for_exec_fut<'a>(_: Self::ExecFut<'a>) { }
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ dummy function
}
As a side note, there is another issue linked - Problems with GATs, async, and Send-bounds - that describes an odd interaction between the bounds like above and Send
which may explain why removing a Send
bound caused the snippet to compile. Still weird.