Raywell
Raywell

Reputation: 187

Rust lifetime issue with closures within same global scope

Here's the minimal representation of the problem in my code :

use std::collections::HashMap;
fn main() {
  let mut map : HashMap<String, String> = HashMap::new();
  map.insert(String::from("a"), String::from("first"));
  map.insert(String::from("b"), String::from("second"));

  let mut funcs: Vec<Box<dyn FnMut(String) -> ()>> = Vec::new(); 
  for (key, val) in map { 
    funcs.push(Box::new(|v| {
      println!("{} : {} : {}",key,val,v);
    }))
  }

  for mut func in funcs {
    func(String::from("c"));
  }
}

The error : (same for val) :

`key` does not live long enough
values in a scope are dropped in the opposite order they are defined rustcE0597
main.rs(12, 5): `key` dropped here while still borrowed
main.rs(9, 29): value captured here
main.rs(17, 1): borrow might be used here, when `funcs` is dropped and runs the 
`Drop` code for type `Vec`

This is a minimalist representation of my real life use case - I'm using a library which requires me to feed it a bunch of closures for an combined execution later on. I first build a HashMap, then loop over it to create a list of FnMut (in this minimal example Fn would also be fine)

I get the general idea of the error that I'm creating key (and val) with temporary lifespan which ends at the end of each loop, and the closures have a "deferred" execution where they will want to access this data which will no longer exist - but I have no idea how to fix it. I tried map.into_iter(), map.drain() (to not use references), wrapping the data with Rc - without avail. What is the correct solution here ?

Since building the map is a somewhat complex process, I'd like to still have it built before making closures if possible.

EDIT1:

The solution to add move in front of the closure by @Masklinn does work here, but it creates other issues in my real code

EDIT2: Illustration of second problem when using move :

fn main() {
let mut map : HashMap<String, Vec<String>> = HashMap::new();
map.insert(String::from("a"), Vec::from([String::from("first")]));
map.insert(String::from("b"), Vec::from([String::from("first")]));

let map2 : HashMap<String, Vec<String>> = HashMap::new();

let mut funcs: Vec<Box<dyn FnMut(String) -> ()>> = Vec::new(); 
for (key, val) in map { 
    funcs.push(Box::new(move |v| {
        println!("{} : {:?} : {}, {}",key,val,v, map2.capacity());
    }))
}

for mut func in funcs {
    func(String::from("c"));
}

}

Error:

use of moved value: `map2`
value moved into closure here, in previous iteration of looprustcE0382
main.rs(12, 54): use occurs due to use in closure
main.rs(7, 9): move occurs because `map2` has type `HashMap<String, Vec<String>>`, which does not implement the `Copy` trait

Should I only be using copyable data (if yes, how ? Wrap the hashmap into a custom copyable struct?), or is there another way?

Upvotes: 1

Views: 233

Answers (2)

vvvvv
vvvvv

Reputation: 31569

Thanks to @Masklinn: wrapping non-copyable data into Rc works perfectly!

use std::{collections::HashMap, rc::Rc};
fn main() {
    let mut map : HashMap<String, Vec<String>> = HashMap::new();
    map.insert(String::from("a"), Vec::from([String::from("first")]));
    map.insert(String::from("b"), Vec::from([String::from("first")]));

    let map2 : HashMap<String, Vec<String>> = HashMap::new();
    let rc_map2: Rc<HashMap<String, Vec<String>>> = Rc::new(map2);

    let mut funcs: Vec<Box<dyn FnMut(String) -> ()>> = Vec::new(); 
    for (key, val) in map {
        let rc_map2_clone = rc_map2.clone();
        funcs.push(Box::new(move |v| {
            println!("{} : {:?} : {}, {}",key,val,v, rc_map2_clone.capacity());
        }))
    }

    for mut func in funcs {
        func(String::from("c"));
    }
}

This answer was posted as an edit to the question Rust lifetime issue with closures within same global scope by the OP Raywell under CC BY-SA 4.0.

Upvotes: 0

Masklinn
Masklinn

Reputation: 42207

What is the correct solution here ?

Just add the keyword move before the closure:

    funcs.push(Box::new(move |v| {
      println!("{} : {} : {}",key,val,v);
    }))

By default, lambdas try to infer how the variable closed over should be captured (reference, mutable reference, or value). However this is based exclusively on usage.

Here you're just printing the key and value, so the usage requirements are just shared references, therefore inside the closure you have key: &String and val: &String.

By using move closures, you're telling the lambda to always capture by value (and you'll manage capturing e.g. references by value).

By using a move closure here we're making the closure own the key and value Strings, therefore their lifetimes are not an issue anymore.

Upvotes: 3

Related Questions