5

I have the following minimal example of my code:

fn main()
{
    let names : Vec<Vec<String>> = vec![
        vec!["Foo1".to_string(), "Foo2".to_string()],
        vec!["Bar1".to_string(), "Bar2".to_string()]
    ];
    let ids : Vec<i64> = vec![10, 20];

    names.iter().enumerate().flat_map(|(i,v)| {
        let id : i64 = ids[i];
        v.iter().map(|n| 
            (n.clone(), id)
        )
    });
}

Now, when I compile that with rustc I get the following error message:

error[E0597]: `id` does not live long enough
  --> main.rs:12:16
   |
11 |         v.iter().map(|n| 
   |                      --- capture occurs here
12 |             (n.clone(), id)
   |                         ^^ borrowed value does not live long enough
13 |         )
14 |     });
   |     -- borrowed value needs to live until here
   |     |
   |     borrowed value only lives until here

But in my understanding, id is of type i64 and should therefore be able to be copied into the capture, with would be exactly what I need?

I've also tried to inline the id variable but to no avail:

error[E0597]: `i` does not live long enough
  --> main.rs:11:21
   |
10 |             v.iter().map(|n| 
   |                          --- capture occurs here
11 |                 (n.clone(), ids[i])
   |                                 ^ borrowed value does not live long enough
12 |             )
13 |         });
   |         -- borrowed value needs to live until here
   |         |
   |         borrowed value only lives until here

So how can I copy my integer into the closure instead of borrowing it?

I tried using move, but rustc doesn't like that either:

error[E0507]: cannot move out of captured outer variable in an `FnMut` closure
  --> main.rs:10:17
   |
7  |         let ids : Vec<i64> = vec![10, 20];
   |             --- captured outer variable
...
10 |             v.iter().map(move |n| 
   |                          ^^^^^^^^ cannot move out of captured outer variable in an `FnMut` closure

So I'd somehow need to get rustc to only move/copy some but not the other variable?

msrd0
  • 7,816
  • 9
  • 47
  • 82
  • 2
    Use `move`. Copyable things are not moved but copied. Let me find the duplicate. – Boiethios Jan 21 '19 at 10:18
  • @Boiethios doesn't work either, I'll add the error – msrd0 Jan 21 '19 at 10:20
  • Add it to both your closures – Boiethios Jan 21 '19 at 10:20
  • I just answered, the question for quick help, Can be happily vote for if it is duplicate – Akiner Alkan Jan 21 '19 at 10:22
  • @Boiethios Same error if I add to both closures. First version works like that as in AkinerAlkan's answer, but can I also get the second version to work that way? – msrd0 Jan 21 '19 at 10:22
  • It works pretty well: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=95da9639666f29e055ab5fb75a912436 – Boiethios Jan 21 '19 at 10:23
  • @Boiethios it doesn't when you inline `id`: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=de2261586d535305991492d18770bbc0 – msrd0 Jan 21 '19 at 10:24
  • @Boiethios but as I understand, if I borrow `ids : Vec` I should be just fine getting one of the `i64`'s out and copy it into my return tuple? – msrd0 Jan 21 '19 at 10:28
  • What is your actual question? Is it "how to inline `id`"? – Boiethios Jan 21 '19 at 10:31
  • @Boiethios Well, I'm rather trying to understand why it doesn't allow me to write the code in question, especially why it says that `i` needs to live until outside the capture when the return value of `ids[i]` should not contain a reference to `i` anymore – msrd0 Jan 21 '19 at 10:34
  • Based on your error messages, you are still using Rust 2015. I **strongly** encourage you to switch to 2018 for improved error messages. – Shepmaster Jan 21 '19 at 15:11

2 Answers2

4

When you create a closure in Rust, it captures the variables either by value or by reference. A mix of both is impossible. By default, it captures by reference, but with the move keyword, it captures by value (i.e. it moves the captured variables inside the closure).

So, in your first code, you need to move id inside the closure:

fn main() {
    let names: Vec<Vec<String>> = vec![
        vec!["Foo1".to_string(), "Foo2".to_string()],
        vec!["Bar1".to_string(), "Bar2".to_string()],
    ];
    let ids: Vec<i64> = vec![10, 20];

    names.iter().enumerate().flat_map(|(i, v)| {
        let id: i64 = ids[i];
        v.iter().map(move |n| (n.clone(), id))
    });
}

Then you ask if you can "inline" ids:

fn main() {
    let names: Vec<Vec<String>> = vec![
        vec!["Foo1".to_string(), "Foo2".to_string()],
        vec!["Bar1".to_string(), "Bar2".to_string()],
    ];
    let ids: Vec<i64> = vec![10, 20];

    names.iter().enumerate().flat_map(|(i, v)| {
        v.iter().map(|n| (n.clone(), ids[i]))
    });
}

You cannot put ids at all in your inner closure, because you are already inside a FnMut closure (that requires exclusive access). Thus, you cannot borrow or move ids because it is already borrowed by the FnMut closure. Minimal reproduction:

fn main() {
    let mut i = 0;

    let mut closure = || {
        i = 2;
        || {
            println!("i = {}", i);
        }
    };

    closure()();
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Boiethios
  • 38,438
  • 19
  • 134
  • 183
  • That makes sense, but the compiler points the error message to `i`, not `ids`, so is this just a wrong error message? – msrd0 Jan 21 '19 at 10:51
  • @msrd0 That's another issue: you try to return something that holds a reference to `i`, but `i` is declared from inside your mutable closure. – Boiethios Jan 21 '19 at 10:54
  • Ok I see, because `map` does take ownership of the closure, the compiler cannot guarantee it being executed before `i` runs out of scope. That was not very clear from the error message from the compiler. Thanks – msrd0 Jan 21 '19 at 11:00
  • That's not exactly that: `v.iter().map()` returns a struct `Map` in which there is a reference to the local variable `i`. That's another case of a returned reference to a local variable. – Boiethios Jan 21 '19 at 11:05
  • 2
    I don't think your last paragraph is accurate. You *can* borrow `ids` in the inner closure, it's `i` that can't be borrowed (because it belongs to the outer closure). On the other hand, you can *move* `i` (because it's `Copy`), but you can't move `ids` because the outer closure only borrows it. You need to borrow `ids` while moving `i`, which you can do by [taking a reference to `ids` in the outer closure and using `move` to move both.](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=196fe9c95c7abce220619c846ce7ed8e) – trent Jan 21 '19 at 13:25
  • 2
    *A mix of both is impossible* — this is technically accurate, but as trentcl shows, you can take an explicit reference and then move the reference in, effectively getting a mix. – Shepmaster Jan 21 '19 at 15:14
  • @Shepmaster You understand what I meant. You must add a step to have only movable types (either a copyable type or a reference). I answer to the question: why cant I inline the index stuff. – Boiethios Jan 21 '19 at 15:25
  • `FnMut` is also not relevant in the way you imply. Each closure will only borrow `ids` immutably if it can get away with it, even if it had to borrow the rest of the environment exclusively. Your minimal repro demonstrates a case where exclusive borrowing *is actually required*, but that's not true in the original code. – trent Jan 21 '19 at 15:56
  • A mix of both is impossible - this is totally inaccurate AFAIK. Take a look at this very [simple example](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=f6aa77d0b4e2fb3a3538151af72bf2b3). There is no hard rule saying that variables can't be moved into closure, it depends on how the variable is used. In fact some closures can be movable without using `move ||` – Marin Veršić Jun 05 '20 at 20:56
  • The trick in my example is that closure only implements `FnOnce` since the variable is always moved out of the closure's environment upon closure being called. I don't think an example can be made for `Fn` and `FnMut`. Mix of both capture by value and capture by reference is only possible for `FnOnce` closures. – Marin Veršić Jun 05 '20 at 21:25
  • @MarinVeršić Didn't know this was possible. Feel free to either update my answer or create your own one. – Boiethios Jun 06 '20 at 09:39
2

You can move the variable into closure with move keyword. Here you need to change the closure like:

v.iter().map(move |n|  // move is the keyword for moving variables into closure scope.
    (n.clone(), id)
)

Playground

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Akiner Alkan
  • 6,145
  • 3
  • 32
  • 68