I'm trying to join a variable number of futures concurrently, using futures::future::join_all
. Creating the futures and joining them happens in a &mut self
method. Each concurrent task, however, needs to execute a method on &self
.
Working non-asynchronous (and non-concurrently), this works fine:
struct S {
sum: i32,
multiplier: i32,
}
impl S {
fn new(multiplier: i32) -> Self {
S { multiplier, sum: 0 }
}
fn do_something(&self, payload: i32) -> i32 {
println!("Processed: {}", payload);
self.multiplier * payload
}
fn run(&mut self) {
let tasks = (0..3).map(|i| self.do_something(i));
let results = tasks.collect::<Vec<_>>();
for result in results {
self.sum += result;
}
}
}
fn main() {
let mut s = S::new(10);
s.run();
println!("Sum: {}", s.sum);
}
But when I (naïvely) go to async, it wont' work:
(I'll provide a solution later in this thread)
impl S {
fn new(multiplier: i32) -> Self {
S { multiplier, sum: 0 }
}
async fn do_something(&self, payload: i32) -> i32 {
println!("Processed: {}", payload);
self.multiplier * payload
}
async fn run(&mut self) {
let tasks = (0..3).map(|i| async { self.do_something(i).await });
let results = futures::future::join_all(tasks).await;
for result in results {
self.sum += result;
}
}
}
#[tokio::main]
async fn main() {
let mut s = S::new(10);
s.async_run().await;
println!("Sum: {}", s.sum);
}
For some reason, i
gets borrowed (instead of copied). Is that correct behavior of the compiler? I get the following error message:
error[E0373]: async block may outlive the current function, but it borrows `i`, which is owned by the current function
--> src/main.rs:15:42
|
15 | let tasks = (0..3).map(|i| async { self.do_something(i).await });
| ^^^^^^^^^^^^^^^^^^^^-^^^^^^^^^
| | |
| | `i` is borrowed here
| may outlive borrowed value `i`
|
note: async block is returned here
--> src/main.rs:15:36
|
15 | let tasks = (0..3).map(|i| async { self.do_something(i).await });
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: to force the async block to take ownership of `i` (and any other referenced variables), use the `move` keyword
|
15 | let tasks = (0..3).map(|i| async move { self.do_something(i).await });
| ++++
I'm trying to force the move. I learned I can do that by writing let x = x;
so I write:
let tasks = (0..3).map(|i| {
let i: i32 = i; // trying to copy here
async {
let i: i32 = i; // trying to copy here as well
self.do_something(i).await
}
});
But that doesn't work either:
error[E0373]: async block may outlive the current function, but it borrows `i`, which is owned by the current function
--> src/main.rs:17:19
|
17 | async {
| ___________________^
18 | | let i: i32 = i; // trying to copy here as well
| | - `i` is borrowed here
19 | | self.do_something(i).await
20 | | }
| |_____________^ may outlive borrowed value `i`
|
note: async block is returned here
--> src/main.rs:17:13
|
17 | / async {
18 | | let i: i32 = i; // trying to copy here as well
19 | | self.do_something(i).await
20 | | }
| |_____________^
help: to force the async block to take ownership of `i` (and any other referenced variables), use the `move` keyword
|
17 | async move {
| ++++
Yeah, I know, I should add "move
", but I'm not giving up yet!
First let's see what happens if I write *i
. I get:
error[E0614]: type `i32` cannot be dereferenced
--> src/main.rs:18:30
|
18 | let i: i32 = *i; // trying to copy here as well
| ^^
So what!? i
isn't a reference, but an i32. Why can't I copy it?
Perhaps the reason is that the copy doesn't get executed until the async block is polled/awaited? Is that documented somewhere? I doubt it is a flaw in the compiler, so I wonder what's going on here.
One solution is to use async move
. It doesn't turn out trival though:
let tasks = (0..3).map(|i| async move { self.do_something(i).await });
This gives:
error[E0507]: cannot move out of `self`, a captured variable in an `FnMut` closure
…
error[E0382]: use of moved value: `self`
…
Instead I have to do:
async fn run(&mut self) {
let tasks = (0..3).map(|i| {
let this: &_ = self;
async move { this.do_something(i).await}
});
let results = futures::future::join_all(tasks).await;
for result in results {
self.sum += result;
}
}
That works, and I get:
Processed: 0
Processed: 1
Processed: 2
Sum: 30
(Playground with the full example if you want to experiment on it.)
My questions:
- Is it correct that
let x = x;
can force a move for (non-async) closures, but doesn't force a move for anasync
(non-move
) block? If yes, why? And is this documented somewhere? - Is there any way to fix the problem (see playground link above) without using
async move
? This is more a theoretical question. I'm fine to usemove
, but I would like to understand if there are any alternatives. I always thought I can variable-wise override the behaviour withlet x = x;
inside the block (to force move/copy) orlet x = &x;
(to force borrow).