I have the following Rust code
use std::error::Error;
#[derive(Debug)]
enum MyEnum {
First,
Second,
}
fn do_something(index: usize, m: &MyEnum) {
eprintln!("do_something: {} {:?}", index, m);
}
async fn test() -> Result<(), Box<dyn Error>> {
let myvec = vec![MyEnum::First, MyEnum::Second];
let c = String::from("cap");
let futures = myvec.iter().enumerate().map(|(index, el)| async {
eprintln!("Element: {}", &c);
do_something(index, el);
});
futures::future::join_all(futures).await;
Ok(())
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
test().await?;
Ok(())
}
Compiler complains that:
error[E0373]: async block may outlive the current function, but it borrows `index`, which is owned by the current function
--> src/main.rs:17:62
|
17 | let futures = myvec.iter().enumerate().map(|(index, el)| async {
| ^^^^^ may outlive borrowed value `index`
18 | eprintln!("Element: {}", &c);
19 | do_something(index, el);
| ----- `index` is borrowed here
|
Why it's not complaining about the el
variable?
And how to fix this code? I would like to keep using the iterator and not for
loop.
I can change other parts of code, e.g. do_something()
can have different prototype.
I've put the c
variable here to forbid the easy solution with async move {...}
. The string c
should not be moved.
Without enumerate()
(and without index
) it works. I would expect that index
(which has usize
type) is easily copy-able.
I would expect that index (which has
usize
type) is easily copy-able.
That's exactly the problem, because usize
is copy, for it to move the compiler only needs to capture a shared reference it can just copy the bytes when it needs a value. It'll use the weakest kind of capture it can, first &
, then &mut
and only if these don't work it'll move.
The problem is that index
is a local to the closure, which is gone when it returns the future.
el
doesn't have that same problem because it is a reference to the outer myvec
which is owned by test
, which does live long enough, hence any references to it also can live long enough.
To fix it you simply apply the "easy solution" you found yourself, any captures that you do not want to move you can simply shadow1 with the desired binding kind (&
or &mut
):
async fn test() -> Result<(), Box<dyn Error>> {
let myvec = vec![MyEnum::First, MyEnum::Second];
let c = String::from("cap");
let futures = myvec.iter().enumerate().map({
let c = &c;
move |(index, el)| async move {
eprintln!("Element: {c}");
do_something(index, el);
}
});
futures::future::join_all(futures).await;
eprintln!("c is still available: {c}");
Ok(())
}
1 of course you don't have to shadow it, but shadowing allows you to not worry about a new name and is quite idiomatic here.