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

Wrong error: cannot move out of captured outer variable in an FnMut closure #47850

Open
Boscop opened this issue Jan 29, 2018 · 5 comments
Open
Labels
A-borrow-checker Area: The borrow checker A-closures Area: closures (`|args| { .. }`) A-diagnostics Area: Messages for errors, warnings, and lints C-enhancement Category: An issue proposing an enhancement or a PR with one. D-terse Diagnostics: An error or lint that doesn't give enough information about the problem at hand. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@Boscop
Copy link

Boscop commented Jan 29, 2018

error: cannot move out of captured outer variable in an FnMut closure

struct Foo {
    a: i32,
    b: i32,
    bar: Bar,
}

struct Bar;

impl Bar {
    fn f<F: FnMut()>(&self, mut f: F) {
        f();
    }
}

fn main() {
    let mut foo = Foo { a: 1, b: 2, bar: Bar };
    let a = &mut foo.a;
    let b = &mut foo.b;
    (|| { // works
        *if true {a} else {b} = 42;
    })();
    
    let mut foo = Foo { a: 1, b: 2, bar: Bar };
    let a = &mut foo.a;
    let b = &mut foo.b;
    foo.bar.f(|| { // doesn't work
        *if true {a} else {b} = 42;
    });
}

https://play.rust-lang.org/?gist=4ce6948a92c2fcb281b3cade8574691d&version=nightly

But the second case should work too!

@kennytm
Copy link
Member

kennytm commented Jan 29, 2018

It works if you change the FnMut to FnOnce.

@Boscop
Copy link
Author

Boscop commented Jan 29, 2018

But why can't it also work with FnMut?

(Btw, the error should say it has to be FnOnce then.)

@kennytm kennytm added A-diagnostics Area: Messages for errors, warnings, and lints A-borrow-checker Area: The borrow checker labels Jan 29, 2018
@nikomatsakis
Copy link
Contributor

nikomatsakis commented Jan 29, 2018

@Boscop because you can only move things one time -- an FnMut closure can be invoked more than once.

(Btw, the error should say it has to be FnOnce then.)

I don't entirely agree, but I also don't disagree. This kind of error is often hard to decide how to diagnose. There is a kind of tension here: the closure wants to take an action (a move), but the signature of f does not permit it (because it requires a closure that can be invoked more than once). It may be that the signature is in error (it should be generalized to accept an FnOnce) or that the closure is wrong (it is trying to do things it should not do). At minimum, we're clearly not doing a good job of capturing this 'tension' in the diagnostic.

Interestingly, if the closure is not given directly as an argument to f we give a somewhat better error:

error[E0525]: expected a closure that implements the `FnMut` trait, but this closure only implements `FnOnce`
  --> src/main.rs:19:19
   |
19 |       let closure = || { // doesn't work
   |  ___________________^
20 | |         *if true {a} else {b} = 42;
21 | |     };
   | |_____^
22 |       foo.bar.f(closure);
   |               - the requirement to implement `FnMut` derives from here
   |
note: closure is `FnOnce` because it moves the variable `a` out of its environment
  --> src/main.rs:20:19
   |
20 |         *if true {a} else {b} = 42;
   |                

This is because of the tracking that @estebank (I think? or was it someone else...) put in to track why we decide a closure's "defining trait". Right now, that checking is not coming into play because we are deciding that the closure should be FnMut solely because of the signature of f.

That happens in this code:

if let Some(kind) = opt_kind {
self.demand_eqtype(expr.span,
kind.to_ty(self.tcx),
substs.closure_kind_ty(expr_def_id, self.tcx));
}

When we go down that path, we never wind up storing the "origin" of the kind. In contrast, the upvar inference that would otherwise kick in does this:

// If we have an origin, store it.
if let Some(origin) = delegate.current_origin {
self.tables
.borrow_mut()
.closure_kind_origins_mut()
.insert(closure_hir_id, origin);
}

So we probably need to be storing some kind of information in closure_kind_origins_mut and then improving the borrowcheck. Then we could give an error like:

error[E0507]: cannot move out of captured outer variable in an `FnMut` closure
  --> src/main.rs:20:19
   |
17 |     let a = &mut foo.a;
   |         - captured outer variable
...
20 |         *if true {a} else {b} = 42;
   |                   ^ cannot move out of captured outer variable in an `FnMut` closure
note: closure is `FnMut` because of the requirements of `f()`
  --> src/main.rs:19:19
   |
19 |         foo.bar.f(||
   |         ^^^^^^^^^
   |            

@nikomatsakis nikomatsakis added the T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. label Jan 29, 2018
@TimNN TimNN added the C-enhancement Category: An issue proposing an enhancement or a PR with one. label Jan 30, 2018
@estebank
Copy link
Contributor

Current output:

error[E0507]: cannot move out of `a`, a captured variable in an `FnMut` closure
  --> src/main.rs:27:19
   |
24 |     let a = &mut foo.a;
   |         - captured outer variable
25 |     let b = &mut foo.b;
26 |     foo.bar.f(|| { // doesn't work
   |               -- captured by this `FnMut` closure
27 |         *if true {a} else {b} = 42;
   |                   ^ move occurs because `a` has type `&mut i32`, which does not implement the `Copy` trait

error[E0507]: cannot move out of `b`, a captured variable in an `FnMut` closure
  --> src/main.rs:27:28
   |
25 |     let b = &mut foo.b;
   |         - captured outer variable
26 |     foo.bar.f(|| { // doesn't work
   |               -- captured by this `FnMut` closure
27 |         *if true {a} else {b} = 42;
   |                            ^ move occurs because `b` has type `&mut i32`, which does not implement the `Copy` trait
error[E0525]: expected a closure that implements the `FnMut` trait, but this closure only implements `FnOnce`
  --> src/main.rs:19:19
   |
19 |     let closure = || { // doesn't work
   |                   ^^ this closure implements `FnOnce`, not `FnMut`
20 |         *if true {a} else {b} = 42;
   |                   - closure is `FnOnce` because it moves the variable `a` out of its environment
21 |     };
22 |     foo.bar.f(closure);
   |             - ------- the requirement to implement `FnMut` derives from here
   |             |
   |             required by a bound introduced by this call
   |
note: required by a bound in `Bar::f`
  --> src/main.rs:10:13
   |
10 |     fn f<F: FnMut()>(&self, mut f: F) {
   |             ^^^^^^^ required by this bound in `Bar::f`

@estebank estebank added the D-terse Diagnostics: An error or lint that doesn't give enough information about the problem at hand. label Nov 11, 2022
@Boscop
Copy link
Author

Boscop commented Dec 19, 2022

I came across this issue again..
The code below compiles and is the intended thing to do, but rustc doesn't mention this solution, I think it should.
Reasoning: This solution doesn't require changing any of the involved types, so ideally rustc should suggest this.

    foo.bar.f(|| {
        let a = &mut *a;
        let b = &mut *b;
        *if true {a} else {b} = 42;
    });

@estebank estebank added the A-closures Area: closures (`|args| { .. }`) label Dec 21, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-borrow-checker Area: The borrow checker A-closures Area: closures (`|args| { .. }`) A-diagnostics Area: Messages for errors, warnings, and lints C-enhancement Category: An issue proposing an enhancement or a PR with one. D-terse Diagnostics: An error or lint that doesn't give enough information about the problem at hand. 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

5 participants