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