-
Notifications
You must be signed in to change notification settings - Fork 12.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
async lambda fails to copy a value that is Copy #127019
Comments
You may move some things and reference some things by creating a variable that's a reference and then moving that. For example: async fn sum(idx: usize, num: u8) {
let sum = idx + num as usize;
println!("Sum = {}", sum);
}
async fn test() {
let other_value = 1;
let other_value_ref = &other_value;
let results = futures::future::join_all(
(0..10).into_iter()
.enumerate()
.map(|(idx, num)| {
async move {
sum(idx, num + other_value_ref).await;
}
})
).await;
dbg!(results);
} |
@theemathas thanks for the reply. (No offense) I honestly think that reference in your code is a bit awkward, because we need to do extra work that is conceptually a no-op. I actually just tested my example with nightly rustc. It gave a more self-consistent error by saying that both
My rustc is
I also tried your code with this nightly rustc. It turns out the same compile error. |
Compiles if changed to
|
@fmease thanks!
This works for this example which I intentionally reduced a lot. My actual code is a bit complicated. And I think it does not generalize to all use cases of closures.
Yes, the blog post covers a more generalized and complicated scenarios in which the data may not be a plain old number. But I still don't know why rustc can't just copy that plain old data for me. I think my intent expressed in the code should be clear to the compiler and I think it's totally safe and sound to do that. |
@ifsheldon Right, makes sense. I can't answer your more general question right now about capturing copyables by move by default (I'm short on time). Have you tried |
OK, I should not have reduced my code too much as I thought other details were not relevant. A more complete yet reduced code look like this. 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` that is super large and I would rather not clone
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);
} Yes I can workaround this with more code refactoring (and I did), but I think focusing solving this case in this code is a bit too restricted and shortsighted. This issue is not about how to workaround a problem but why this problem (rustc can't copy a Copyable for me when using async closures) happens. |
You should probably use ctrl+f the line |
Also ironically the code ICEs with async closures ( |
…v-shim, r=oli-obk Fix `FnMut::call_mut`/`Fn::call` shim for async closures that capture references I adjusted async closures to be able to implement `Fn` and `FnMut` *even if* they capture references, as long as those references did not need to borrow data from the closure captures themselves. See rust-lang#125259. However, when I did this, I didn't actually relax an assertion in the `build_construct_coroutine_by_move_shim` shim code, which builds the `Fn`/`FnMut`/`FnOnce` implementations for async closures. Therefore, if we actually tried to *call* `FnMut`/`Fn` on async closures, it would ICE. This PR adjusts this assertion to ensure that we only capture immutable references in closures if they implement `Fn`/`FnMut`. It also adds a bunch of tests and makes more of the async-closure tests into `build-pass` since we often care about these tests actually generating the right closure shims and stuff. I think it might be excessive to *always* use build-pass here, but 🤷 it's not that big of a deal. Fixes rust-lang#127019 Fixes rust-lang#127012 r? oli-obk
…v-shim, r=oli-obk Fix `FnMut::call_mut`/`Fn::call` shim for async closures that capture references I adjusted async closures to be able to implement `Fn` and `FnMut` *even if* they capture references, as long as those references did not need to borrow data from the closure captures themselves. See rust-lang#125259. However, when I did this, I didn't actually relax an assertion in the `build_construct_coroutine_by_move_shim` shim code, which builds the `Fn`/`FnMut`/`FnOnce` implementations for async closures. Therefore, if we actually tried to *call* `FnMut`/`Fn` on async closures, it would ICE. This PR adjusts this assertion to ensure that we only capture immutable references in closures if they implement `Fn`/`FnMut`. It also adds a bunch of tests and makes more of the async-closure tests into `build-pass` since we often care about these tests actually generating the right closure shims and stuff. I think it might be excessive to *always* use build-pass here, but 🤷 it's not that big of a deal. Fixes rust-lang#127019 Fixes rust-lang#127012 r? oli-obk
@compiler-errors Respect 🫡 but I've updated nightly just now. My code seems still not to pass rustc check. My nightly is
which should include #127244. Yet the problem remains
|
Oh yeah, sorry,I should've probably not marked this issue as "fixed" since it's only fixed by using async closures: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=418d9b8f3162a865b57d725c43458ad8 (which still ICEs since it's on yesterday's nightly lol) Notice that I've turned The only thing I fixed with that PR was async closures ICEing. |
The compiler is necessarily conservative in what it chooses to capture by-ref or by-move. It turns out that unless you write This is unfortunately an observable detail in the closure capture analysis algorithm, so I don't think it's possible to change. So you're just gonna have to write |
OK, fair enough, but does that means we need to wait for something like Or, can we make a little exception for builtin numeric types like |
I tried this code:
I expected to see this get compiled, but it gave me
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 isCopy
.I also tried other simple numeric types but they didn't work either.
Interestingly, rustc didn't raise an error for
idx
even whennum
is alsousize
, 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
:Updates
Update 0629: A more complete version that is closer to my actual code and cannot simply be resolved by adding
move
isThe text was updated successfully, but these errors were encountered: