Skip to content
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

Duplicating mut Copy variables doesn't warn or error #73467

Open
rocallahan opened this issue Jun 18, 2020 · 8 comments
Open

Duplicating mut Copy variables doesn't warn or error #73467

rocallahan opened this issue Jun 18, 2020 · 8 comments
Labels
A-closures Area: Closures (`|…| { … }`) A-lints Area: Lints (warnings about flaws in source code) such as unused_mut. C-bug Category: This is a bug. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@rocallahan
Copy link

rocallahan commented Jun 18, 2020

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=6dee1348129e0c1938c67eb66a8f62b0

fn main() {
    let mut x = 0;
    let closure = move || {
        let mut closure2 = move || {
            x += 1;
        };
        closure2();
        println!("{}", x);
    };
    closure();
}

The inner closure essentially forks the mutable variable x into two variables. I would expect rustc to give a warning or error, but there is no warning or error. I understand the logic behind allowing this but I think it can lead to surprising results. rust-analyzer warns that assignment to x in closure2 is never read. This led me to a real bug in our code.

Seems to me that duplicating a mut Copy variable like this deserves some kind of warning at least.

@rocallahan rocallahan added the C-bug Category: This is a bug. label Jun 18, 2020
@pitaj
Copy link
Contributor

pitaj commented Jun 18, 2020

Looks like this is already a warning in 1.45.0-beta2 (and nightly ofc)

warning: unused variable: `x`
 --> src/main.rs:5:13
  |
5 |             x += 1;
  |             ^
  |
  = note: `#[warn(unused_variables)]` on by default
  = help: did you mean to capture by reference instead?

@jonas-schievink
Copy link
Contributor

Closing as fixed

@rocallahan
Copy link
Author

I actually think this should be reopened. Here's a slight variation that gives no warning on nightly:
https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=acc65b891ae68e390f0ed94e76df458b

fn main() {
    let mut x = 0;
    let closure = move || {
        let mut closure2 = move || {
            x += 1;
            x
        };
        closure2();
        println!("{}", x);
    };
    closure();
}

Since closure2 is now using its duplicate of x, the unused-variable warning goes away. However, I think it's still a problem that x is being duplicated here.

@IAkumaI
Copy link

IAkumaI commented Feb 24, 2023

Two years passed and this issue is still without any attention.
Much more simple example and topic which describes it: https://users.rust-lang.org/t/why-passing-mut-variable-to-another-thread-isnt-an-error/89857

rustc 1.67.0 (fc594f1 2023-01-24)

let mut a = 1;
for _ in 0..10 {
    tokio::spawn(async move {
        a += 1;
        if a > 3 {
            // this will never reached
        }
    })
    .await
    .unwrap();
}

UPD: Edited to more realistic example

@kpreid
Copy link
Contributor

kpreid commented Feb 24, 2023

I've also myself managed to hit this issue while trying to hurriedly set up a closure producing diagnostic output ("how many times did this event happen" or similar) in addition to its existing behavior.

@IAkumaI
Copy link

IAkumaI commented Feb 24, 2023

@kpreid Yeah, I found the real bug today too. It had to be an archiver which slices zips to 512 Mb each, but it didn't. And it took some time to realize that count the zip size in mut usize which increments in spawn_blocking was not the best idea.

@fmease fmease added A-lints Area: Lints (warnings about flaws in source code) such as unused_mut. A-closures Area: Closures (`|…| { … }`) T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. and removed needs-triage-legacy labels Sep 6, 2023
@Pzixel
Copy link

Pzixel commented Oct 3, 2023

A simpler example from my recent code:

let mut i = 0;
let mut f = move || {
  i += 1;
  i
};
println!("{}", f());
println!("{}", i);

Which formerly was

#[tokio::main(flavor = "current_thread")]
async fn main() {
    let mut i = 0;

    let n = 5;
    let futures = (0..n).map(|_| async move {
        // some dozens of lines of code here
        i += 1;
        println!("Done {} out of {}", i, n);
    });

    for f in futures {
        f.await;
    }
}

The thing is that counter didn't exist in the fist place - it was just bunch of futures with their own work. So I added a counter - and while it stroke me a little bit "why does it compile? Don't I need some kind of synchronization" I immediately thought "well I'm running on a current-thread executor, so it figured out I don't need synchronization and regular reference is enough, since compiler is happy then it most definitely should be valid". And it's easy to imagine I was very wrong about that. I agree that I was tired and probably should have looked better, but in the end I really think that compiler should warn about such issues, C# compiler does just this in a very similar situation. I think this is the best approach and doesn't harm anyone - rust always has been about explicitness so I think this is a great place to apply it.

@kpreid
Copy link
Contributor

kpreid commented Jul 23, 2024

This came up on URLO again.

I think what the compiler should do is warn when, for some binding foo,

  • a closure captures foo by value,
  • the captured foo is mutated within the closure, and
  • the original foo is used after the closure was constructed.

Note that these rules don’t mention Copy. Rather, they describe a situation which is already an error if foo's type is not Copy, so they should hopefully preempt this surprise that arises only with Copy. Also, if a non-move closure writes || { let mut foo = foo; ... } to force a move/copy, then that naturally silences the warning since the new shadowing binding isn't the capture.

@fmease fmease added A-lints Area: Lints (warnings about flaws in source code) such as unused_mut. and removed A-lints Area: Lints (warnings about flaws in source code) such as unused_mut. labels Dec 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-closures Area: Closures (`|…| { … }`) A-lints Area: Lints (warnings about flaws in source code) such as unused_mut. C-bug Category: This is a bug. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

8 participants