The following program compiles without issue:
#[tokio::main]
async fn main() {
async fn g(x: String) {}
let f = || {
let y: String = String::from("a").clone();
return async {
println!("{}", &y);
return g(y).await;
}};
}
However, if the line "return g(y).await;" is removed, it will fail with the following:
error[E0373]: async block may outlive the current function, but it borrows `y`, which is owned by the current function
--> src/main.rs:35:22
|
35 | return async {
| ______________________^
36 | | println!("{}", &y);
| | - `y` is borrowed here
37 | | // return g(y).await;
38 | | }};
| |_____^ may outlive borrowed value `y`
|
note: async block is returned here
--> src/main.rs:35:16
|
35 | return async {
| ________________^
36 | | println!("{}", &y);
37 | | // return g(y).await;
38 | | }};
| |_____^
help: to force the async block to take ownership of `y` (and any other referenced variables), use the `move` keyword
|
35 | return async move {
| ++++
Why does the same error not appear in the original code?
Rust does the minimum amount of work necessary to get your closure to work.
let f = || {
let y: String = String::from("a").clone();
return async {
println!("{}", &y);
}
};
Here, the inner closure requires y
by reference. So Rust is going to turn it into, essentially, a struct
with a &String
. Removing the async stuff for simplicity, it would turn this
let f = || {
let y: String = String::from("a").clone();
|| {
println!("{}", &y);
}
};
into, effectively, this
struct MyCustomClosure<'a> { y: &'a String };
impl<'a> FnOnce for MyCustomClosure<'a> {
fn call_once(self) {
println!("{}", self.y)
}
}
// (Same impl for FnMut and Fn ...)
let f = || {
let y: String = String::from("a").clone();
return MyCustomClosure { y: &y }
};
Now, sometime way later on in the compilation process, Rust realizes that the lifetime 'a
for MyCustomClosure
doesn't line up with the lifetime for the enclosing function, and it complains. But by this point it's already committed to using a reference here and it's not smart enough to go back and try a different closure type. It's two different stages of compilation that don't talk to each other directly.
This on the other hand
let f = || {
let y: String = String::from("a").clone();
|| { y }
};
This, on the other hand, very clearly requires a move. We're passing ownership inside the closure, so we get a closure that only implements FnOnce
and that takes the y
by value. Essentially we get
struct MyCustomClosure2 { y: String };
impl FnOnce for MyCustomClosure2 {
fn call_once(self) -> String {
self.y
}
}
// No FnMut or Fn this time, since we need to pass ownership of a value.
Now there's no lifetime argument 'a
to cause conflicts down the road. There's just a simple struct and it all works out.
As the error message indicates, if your intent is to get an FnOnce
which returns the string by moving, you can prefix your closure with the move
keyword.
let f = || {
let y: String = String::from("a").clone();
return async move {
println!("{}", &y);
}
};
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