I was making the executor/reactor while discovered this a lifetime problem. It is not related to async/Future and can be reproduced without async sugar.
use std::future::Future;
struct Runtime;
fn start_with_runtime<C, F>(closure: C)
where
C: for<'a> FnOnce(&'a Runtime) -> F,
F: Future
{
let rt = Runtime;
let _future = closure(&rt);
// block_on(future);
}
async fn async_main(_rt: &Runtime) {
// I can use _rt to do async stuff here
}
fn main() {
start_with_runtime(|rt| { async_main(rt) });
}
I would like to the start_with_runtime()
to run the future and provide the async Runtime reference as parameter.
It does not compile:
error: lifetime may not live long enough
--> src/main.rs:17:31
|
17 | start_with_runtime(|rt| { async_main(rt) });
| --- ^^^^^^^^^^^^^^ returning this value requires that `'1` must outlive `'2`
| | |
| | return type of closure is impl std::future::Future
| has type `&'1 Runtime`
I think that this problem seems to be because how rust infers lifetimes for closures:
https://github.com/rust-lang/rust/issues/58052 :
fn main() {
let f = |x: &i32| x;
let i = &3;
let j = f(i);
}
Does not compiles either:
error: lifetime may not live long enough
--> src/main.rs:2:23
|
2 | let f = |x: &i32| x;
| - - ^ returning this value requires that `'1` must outlive `'2`
| | |
| | return type of closure is &'2 i32
| let's call the lifetime of this reference `'1`
Looks like my closure signature inferred as |&'a Runtime| -> impl Future + 'b
and thus the lifetime error. I feel that given correct expected signature for closure would help, but how do I provide the correct signature in start_with_runtime
?
fn start_with_runtime<C>(closure: C)
where
C: for<'a> FnOnce(&'a Runtime) -> (impl Future + 'a),
Does not work because impl Trait
is not allowed here.
fn start_with_runtime<C,F>(closure: C)
where
C: for<'a> FnOnce(&'a Runtime) -> F,
F: Future + 'a
Does not work as well because 'a
is not known outside of HRTB expression.
It works if I know the type:
struct MyType<'a> {
_rt: &'a Runtime
}
fn start_with_runtime<C>(closure: C)
where
C: for<'a> FnOnce(&'a Runtime) -> MyType<'a>,
This is kind of sad when you've thought through all the lifetimes but language does not provide a way to express this. Perhaps there is a trick in rust to make this work?
Lifetimes are what the Rust compiler uses to keep track of how long references are valid for. Checking references is one of the borrow checker's main responsibilities. Lifetimes help the borrow checker ensure that you never have invalid references.
Explicit lifetimes allow the compiler to ensure that your program is doing what you intended. If you (the compiler) want(s) to check if a piece of code is valid, you (the compiler) will not have to iteratively look inside every function called.
A closure type is approximately equivalent to a struct which contains the captured variables. For instance, the following closure: #![ allow(unused)] fn main() { fn f<F : FnOnce() -> String> (g: F) { println!
Rust closures are harder for three main reasons: The first is that it is both statically and strongly typed, so we'll need to explicitly annotate these function types. Second, Lua functions are dynamically allocated ('boxed'.)
There seem to be two different questions in this one: can the required relationship be expressed in Rust syntax and will it work with closure type inference or not.
Let's start with the first one. You're right that this cannot be expressed with just where
clauses. To express this one needs to add a helper trait
trait BorrowingFn<'a> {
type Fut: std::future::Future<Output = Something> + 'a;
fn call(self, arg: &'a Runtime) -> Self::Fut;
}
that allows the bound we need to be written as
C: for<'a> BorrowingFn<'a>,
and provide a blanket implementation of this trait for all applicable functions
impl<'a, Fu: 'a, F> BorrowingFn<'a> for F
where
F: FnOnce(&'a Runtime) -> Fu,
Fu: std::future::Future<Output = ()> + 'a,
{
type Fut = Fu;
fn call(self, rt: &'a Runtime) -> Fu {
self(rt)
}
}
(playground)
Ok, so it works with an async function, but does it work with a closure that needs type inference? Unfortunately, the answer is "no"
error: implementation of `BorrowingFn` is not general enough
--> src/main.rs:33:5
|
5 | / trait BorrowingFn<'a> {
6 | | type Fut: std::future::Future<Output = ()> + 'a;
7 | | fn call(self, arg: &'a Runtime) -> Self::Fut;
8 | | }
| |_- trait `BorrowingFn` defined here
...
33 | start_with_runtime(|rt| async_main(rt)); // however, it does not work with closure type inference :-(
| ^^^^^^^^^^^^^^^^^^ implementation of `BorrowingFn` is not general enough
|
= note: `[closure@src/main.rs:33:24: 33:43]` must implement `BorrowingFn<'0>`, for any lifetime `'0`...
= note: ...but `[closure@src/main.rs:33:24: 33:43]` actually implements `BorrowingFn<'1>`, for some specific lifetime `'1`
This is being tracked in rust-lang/rust#70263. The compiler is not smart enough yet to notice that this closure needs a higher-rank type.
For fun I tried compiling with -Z chalk
on Nightly, but it's not yet ready for this (internal compiler error).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With