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); }