async lambda fails to copy a value that is Copy · Issue #127019 · rust-lang/rust (original) (raw)

I tried this code:

async fn sum(idx: usize, num: u8) { let sum = idx + num as usize; println!("Sum = {}", sum); }

async fn test() { let results = futures::future::join_all( (0..10).into_iter() .enumerate() .map(|(idx, num)| { async { sum(idx, num).await; } }) ).await; dbg!(results); }

I expected to see this get compiled, but it gave me

error[E0373]: async block may outlive the current function, but it borrows `num`, which is owned by the current function
   --> src/main.rs:146:17
    |
146 | /                 async {
147 | |                     sum(idx, num).await;
    | |                              --- `num` is borrowed here
148 | |                 }
    | |_________________^ may outlive borrowed value `num`
    |
note: async block is returned here
   --> src/main.rs:146:17
    |
146 | /                 async {
147 | |                     sum(idx, num).await;
148 | |                 }
    | |_________________^
help: to force the async block to take ownership of `num` (and any other referenced variables), use the `move` keyword
    |
146 |                 async move {
    |                       ++++

The suggestion is plausible here, but in my actual code, I cannot easily add move because it also moves other values that I can only borrow and thus breaks my code.

The error makes sense in someway but I think rustc can easily copy num for me since it's just plain old data that is Copy.

I also tried other simple numeric types but they didn't work either.

Interestingly, rustc didn't raise an error for idx even when num is also usize, so I think the error is not self-consistent.

When searching issues, #127012 seems very similar to the code here, but I don't know whether they correlate.

Meta

rustc --version --verbose:

rustc 1.79.0 (129f3b996 2024-06-10)
binary: rustc
commit-hash: 129f3b9964af4d4a709d1383930ade12dfe7c081
commit-date: 2024-06-10
host: aarch64-apple-darwin
release: 1.79.0
LLVM version: 18.1.7

Updates

Update 0629: A more complete version that is closer to my actual code and cannot simply be resolved by adding move is

use std::time::Duration; use tokio::time::sleep;

async fn sum(idx: usize, num: u8) -> usize { // the code logic is a mock for actual computation and // extracted from the closure to inform rustc // that I need idx and num as values so that it should just copy them for me idx + num as usize }

async fn logging_mock(result: usize, id: usize) { println!("Result={}, id = {}", result, id); }

async fn mock_request(result: usize, request_string: String, request_configs: &str) -> Result<(), String> { // request fn consumes request_string and result and references request_configs dbg!("Requesting {} with {} and {}", result, request_string, request_configs); Ok(()) }

#[derive(Debug)] struct Configs { request_template: String, super_large_config: String, some_other_data: String, }

#[tokio::main] async fn main() { let mut configs = Configs { request_template: "hello".to_string(), super_large_config: "world".to_string(), some_other_data: String::new(), }; let results = futures::future::join_all( (0..10).into_iter() .enumerate() .map(|(idx, num)| { async { let s = sum(idx, num).await; // comment out the above line and simple mocks below make the code compiled // let s = 1; // let idx = 1; let template = configs.request_template.clone(); mock_request(s, template, &configs.super_large_config).await.unwrap(); // non-emergent logging, prevents accidental DoS attack sleep(Duration::from_millis(idx as u64)).await; logging_mock(s, idx).await; } }) ).await; configs.some_other_data.push_str("do something to configs"); dbg!(configs); dbg!(results); }