-
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
Move closure copies :Copy variables silently #63220
Comments
I've just found a real bug in a library caused by the lack of an error/warning for this problem in rustc/Clippy. Simplified slightly the problem is: fn main() {
let mut x = 0;
let mut y = move |z: u32| {
x += z;
println!("inner x {}", x);
};
println!("outer x {}", x);
y(2);
println!("outer x {}", x);
} This outputs:
The problem in this case was moving single-threaded code to being multi-threaded: it compiles fine, but produces incorrect results because ints are fn main() {
let x = 0;
let mut y = move |z: u32| {
let mut x = x + z;
println!("inner x {}", x);
};
println!("outer x {}", x);
y(2);
println!("outer x {}", x);
} There are several mitigations I can imagine (putting aside issues of backwards compatibility):
Maybe there are others I haven't thought of. |
This is also discussed here: https://internals.rust-lang.org/t/closure-value-moved-why-mut-needed/11082 @ltratt Since the closures that are produced are fn main() {
let mut x = 0;
let mut y = move |z: u32| {
x += z;
println!("inner x {}", x);
};
println!("outer x {}", x);
y(2);
y(2);
println!("outer x {}", x);
} This outputs:
Your version with the inner local mutable binding of
For a version that is effectively equivalent you would then need a struct for the captured struct Closure {
x: u32,
}
impl Closure {
fn call_mut(&mut self, z: u32){
self.x += z;
println!("inner x {}", self.x);
}
}
fn main() {
let x = 0;
let mut y = Closure{x};
println!("outer x {}", x);
y.call_mut(2);
y.call_mut(2);
println!("outer x {}", x);
} If I understand it correctly, all of your three suggestions for mitigation implicitly imply that rustc should allow mutating a captured non- |
The reason I called this issue ugly: let mut x = 0;
let mut f = move || x = 42; Is that it implicitly creates a new variable |
@anatol1234 I thought my I don't fully grasp your last paragraph but I think the answer to this bit:
is "no, I don't think that." My starting point is that the current behaviour where a combination of |
These So instead of
And
|
@ltratt In this case, let's assume we had such a warning/error and everything else about rustc remains unchanged. How would you then fix the warning/error in this prominent example taken from the std::iter::from_fn doc: let mut count = 0;
let counter = std::iter::from_fn(move || {
// Increment our count. This is why we started at zero.
count += 1;
// Check to see if we've finished counting or not.
if count < 6 {
Some(count)
} else {
None
}
});
assert_eq!(counter.collect::<Vec<_>>(), &[1, 2, 3, 4, 5]); I hope I don't miss some detail, but I think there is everything in there. An outer variable with |
As I said in the internals thread, it should be thought as the binding is being moved into the closure, not just the value. |
@spunit262 If you apply this logic of moving bindings to reason about the semantics of the Also, in the struct X;
fn main() {
let x = X;
let fnonce = || x;
let x = X;
let fnonce_move = move || x;
} ... would the semantics of |
The Good
Given the following code:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=19261742184207a4075885cae58680e1
I got a reasonable warning:
The Bad
Suggest we have the following code:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=42c989cf63f57e5bad0f572bb705b69f
Neither rustc nor clippy gives me any warning.
x = 42
affects a local copy ofx
, not the outer one, that's why dbg printsx=0
. This can result in a buggy code.The Ugly
async move
closures are affected too. Replacing FnMut's with async move closures light-headedly will add unexpected bugs. Example from https://docs.rs/futures-preview/0.3.0-alpha.17/futures/stream/trait.StreamExt.html#method.for_each:The text was updated successfully, but these errors were encountered: