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

unhelpful error when missing move in nested closure #95079

Closed
lcnr opened this issue Mar 18, 2022 · 4 comments · Fixed by #99612
Closed

unhelpful error when missing move in nested closure #95079

lcnr opened this issue Mar 18, 2022 · 4 comments · Fixed by #99612
Assignees
Labels
A-diagnostics Area: Messages for errors, warnings, and lints T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@lcnr
Copy link
Contributor

lcnr commented Mar 18, 2022

Given the following code: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=1cb8bf9552639abd2a8178105cd3f001

fn foo(s: &str) -> impl Iterator<Item = String> + '_ {
    None.into_iter()
        .flat_map(move |()| s.chars().map(|c| format!("{}{}", c, s)))
}

The current output is:

error: captured variable cannot escape `FnMut` closure body
 --> src/lib.rs:3:29
  |
1 | fn foo(s: &str) -> impl Iterator<Item = String> + '_ {
  |        - variable defined here
2 |     None.into_iter()
3 |         .flat_map(move |()| s.chars().map(|c| format!("{}{}", c, s)))
  |                           - -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |                           | |
  |                           | returns a reference to a captured variable which escapes the closure body
  |                           | variable captured here
  |                           inferred to be a `FnMut` closure
  |
  = note: `FnMut` closures only have access to their captured variables while they are executing...
  = note: ...therefore, they cannot allow references to captured variables to escape

error: could not compile `playground` due to previous error

The actually needed fix here is the following, which should ideally be recommended

fn foo(s: &str) -> impl Iterator<Item = String> + '_ {
    None.into_iter()
        .flat_map(move |()| s.chars().map(move |c| format!("{}{}", c, s)))
}
@lcnr lcnr added A-diagnostics Area: Messages for errors, warnings, and lints T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Mar 18, 2022
@lcnr
Copy link
Contributor Author

lcnr commented Mar 18, 2022

slightly more minimal:

fn foo(s: &str) -> impl Sized + '_ {
    move |()| s.chars().map(|c| format!("{}{}", c, s))
}

@yanchen4791
Copy link
Contributor

@rustbot claim

@yanchen4791
Copy link
Contributor

I came up with a solution of printing an extra line of help: to give user a recommendation of fixing the error by adding 'move' keyword before the closure parameter of map(). The following is the output for the first testing code. Is this good enough?

error: captured variable cannot escape `FnMut` closure body
 --> main_95079_1.rs:3:29
  |
1 | fn foo(s: &str) -> impl Iterator<Item = String> + '_ {
  |        - variable defined here
2 |     None.into_iter()
3 |         .flat_map(move |()| s.chars().map(|c| format!("{}{}", c, s)))
  |                           - -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |                           | |
  |                           | returns a reference to a captured variable which escapes the closure body
  |                           | variable captured here
  |                           inferred to be a `FnMut` closure
  |
  = note: `FnMut` closures only have access to their captured variables while they are executing...
  = note: ...therefore, they cannot allow references to captured variables to escape
  = help: consider adding 'move' keyword before the parameter of closure in `Map<Chars<'_>, [closure@main_95079_1.rs:3:43: 3:68]>`

error: aborting due to previous error

This is the output from the second testing code (slightly more minimal):

error: lifetime may not live long enough
 --> main_95079_2.rs:2:15
  |
2 |     move |()| s.chars().map(|c| format!("{}{}", c, s))
  |     --------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ returning this value requires that `'1` must outlive `'2`
  |     |       |
  |     |       return type of closure `Map<Chars<'_>, [closure@main_95079_2.rs:2:29: 2:54]>` contains a lifetime `'2`
  |     lifetime `'1` represents this closure's body
  |
  = note: closure implements `Fn`, so references to captured variables can't escape the closure
  = help: consider adding 'move' keyword before the parameter of closure in `Map<Chars<'_>, [closure@main_95079_2.rs:2:29: 2:54]>`

error: aborting due to previous error

@yanchen4791
Copy link
Contributor

Refined the fix with a verbose suggestion of where to add move keyword. The output of the first test case is:

error: captured variable cannot escape `FnMut` closure body
 --> /home/ychen/hello_world/src/main_95079_1.rs:3:29
  |
1 | fn foo(s: &str) -> impl Iterator<Item = String> + '_ {
  |        - variable defined here
2 |     None.into_iter()
3 |         .flat_map(move |()| s.chars().map(|c| format!("{}{}", c, s)))
  |                           - -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |                           | |
  |                           | returns a reference to a captured variable which escapes the closure body
  |                           | variable captured here
  |                           inferred to be a `FnMut` closure
  |
  = note: `FnMut` closures only have access to their captured variables while they are executing...
  = note: ...therefore, they cannot allow references to captured variables to escape
help: consider adding 'move' keyword before the nested closure
  |
3 |         .flat_map(move |()| s.chars().map(move |c| format!("{}{}", c, s)))
  |                                           ++++

error: aborting due to previous error

The output of the second test case is:

error: lifetime may not live long enough
 --> /home/ychen/hello_world/src/main_95079_2.rs:2:15
  |
2 |     move |()| s.chars().map(|c| format!("{}{}", c, s))
  |     --------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ returning this value requires that `'1` must outlive `'2`
  |     |       |
  |     |       return type of closure `Map<Chars<'_>, [closure@/home/ychen/hello_world/src/main_95079_2.rs:2:29: 2:32]>` contains a lifetime `'2`
  |     lifetime `'1` represents this closure's body
  |
  = note: closure implements `Fn`, so references to captured variables can't escape the closure
help: consider adding 'move' keyword before the nested closure
  |
2 |     move |()| s.chars().map(move |c| format!("{}{}", c, s))
  |                             ++++

error: aborting due to previous error

In addition, the fix will also add the suggestion for nested closure block like this test case:

fn main() {
    // Field from upvar
    {
        let mut x = 0;
        || {
            let y = &mut x;
            &mut x; //~ ERROR cannot borrow `x` as mutable more than once at a time
            *y = 1;
        };
    }
    // Field from upvar nested
    {
        let mut x = 0;
           || {
               || { //~ ERROR captured variable cannot escape `FnMut` closure body
                   let y = &mut x;
                   &mut x; //~ ERROR cannot borrow `x` as mutable more than once at a time
                   *y = 1;
                   drop(y);
                }
           };
    }
    {
        let f = move || {};
        let _action = move || {
            || f() // The `nested` closure
            //~^ ERROR lifetime may not live long enough
        };
    }
}

The output of the above test case is:

error[E0499]: cannot borrow `x` as mutable more than once at a time
 --> /home/ychen/hello_world/src/main_closue_body.rs:7:13
  |
6 |             let y = &mut x;
  |                     ------ first mutable borrow occurs here
7 |             &mut x; //~ ERROR cannot borrow `x` as mutable more than once at a time
  |             ^^^^^^ second mutable borrow occurs here
8 |             *y = 1;
  |             ------ first borrow later used here

error[E0499]: cannot borrow `x` as mutable more than once at a time
  --> /home/ychen/hello_world/src/main_closue_body.rs:17:20
   |
16 |                    let y = &mut x;
   |                            ------ first mutable borrow occurs here
17 |                    &mut x; //~ ERROR cannot borrow `x` as mutable more than once at a time
   |                    ^^^^^^ second mutable borrow occurs here
18 |                    *y = 1;
   |                    ------ first borrow later used here

error: captured variable cannot escape `FnMut` closure body
  --> /home/ychen/hello_world/src/main_closue_body.rs:15:16
   |
13 |           let mut x = 0;
   |               ----- variable defined here
14 |              || {
   |               - inferred to be a `FnMut` closure
15 | /                || { //~ ERROR captured variable cannot escape `FnMut` closure body
16 | |                    let y = &mut x;
   | |                                 - variable captured here
17 | |                    &mut x; //~ ERROR cannot borrow `x` as mutable more than once at a time
18 | |                    *y = 1;
19 | |                    drop(y);
20 | |                 }
   | |_________________^ returns a closure that contains a reference to a captured variable, which then escapes the closure body
   |
   = note: `FnMut` closures only have access to their captured variables while they are executing...
   = note: ...therefore, they cannot allow references to captured variables to escape
help: consider adding 'move' keyword before the nested closure
   |
15 |                move || { //~ ERROR captured variable cannot escape `FnMut` closure body
   |                ++++

error: lifetime may not live long enough
  --> /home/ychen/hello_world/src/main_closue_body.rs:26:13
   |
25 |         let _action = move || {
   |                       -------
   |                       |     |
   |                       |     return type of closure `[closure@/home/ychen/hello_world/src/main_closue_body.rs:26:13: 26:15]` contains a lifetime `'2`
   |                       lifetime `'1` represents this closure's body
26 |             || f() // The `nested` closure
   |             ^^^^^^ returning this value requires that `'1` must outlive `'2`
   |
   = note: closure implements `Fn`, so references to captured variables can't escape the closure
help: consider adding 'move' keyword before the nested closure
   |
26 |             move || f() // The `nested` closure
   |             ++++

error: aborting due to 4 previous errors

bors added a commit to rust-lang-ci/rust that referenced this issue Aug 16, 2022
…er-errors

Fix rust-lang#95079 unhelpful error when missing move in nested closure

Fix rust-lang#95079 by adding help for missing move in nested closure
@bors bors closed this as completed in 15713e1 Aug 16, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-diagnostics Area: Messages for errors, warnings, and lints T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants