0
votes

I am trying to write a method that, when given an array of keys for a HashMap, will return a Vec containing mutable references of the keys' corresponding values.

Here is the code:

struct Foo {
    bar: HashMap<u32, String>,
}

impl Foo {
    pub fn lookup(&mut self, keys: &[u32]) -> Vec<&mut String> {
        keys.iter()
            .filter(|&x| self.bar.contains_key(x))
            .map(|x| self.bar.get_mut(x).unwrap())
            .collect::<_>()
    }
}

This results in a conflicting lifetime error:

error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
  --> src/main.rs:11:31
   |
11 |             .map(|x| self.bar.get_mut(x).unwrap())
   |                               ^^^^^^^
   |
note: first, the lifetime cannot outlive the lifetime `'_` as defined on the body at 11:18...
  --> src/main.rs:11:18
   |
11 |             .map(|x| self.bar.get_mut(x).unwrap())
   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: ...so that closure can access `self`
  --> src/main.rs:11:22
   |
11 |             .map(|x| self.bar.get_mut(x).unwrap())
   |                      ^^^^^^^^
note: but, the lifetime must be valid for the anonymous lifetime #1 defined on the method body at 8:5...
  --> src/main.rs:8:5
   |
8  | /     pub fn lookup(&mut self, keys: &[u32]) -> Vec<&mut String> {
9  | |         keys.iter()
10 | |             .filter(|&x| self.bar.contains_key(x))
11 | |             .map(|x| self.bar.get_mut(x).unwrap())
12 | |             .collect::<_>()
13 | |     }
   | |_____^
note: ...so that the expression is assignable
  --> src/main.rs:9:9
   |
9  | /         keys.iter()
10 | |             .filter(|&x| self.bar.contains_key(x))
11 | |             .map(|x| self.bar.get_mut(x).unwrap())
12 | |             .collect::<_>()
   | |___________________________^
   = note: expected  `std::vec::Vec<&mut std::string::String>`
              found  `std::vec::Vec<&mut std::string::String>`

Making an immutable version of the function works as intended, though I do not understand why:

pub fn lookup(&self, keys: &[u32]) -> Vec<&String> {
    keys.iter()
        .filter(|&x| self.bar.contains_key(x))
        .map(|x| self.bar.get(x).unwrap())
        .collect::<_>()
}

I am assuming this is a "fighting the borrow checker" issue, possibly due to self.bar being borrowed mutably multiple times. How would I go about fixing this?

1
What do you expect to happen if there are duplicate keys (if it worked as written, you'd get multiple mutable references to the same thing - UB!)?SCappella
Good point. I'll revise it a bit.user24182
That takes care of the problems if the HashMap doesn't contain one of the keys (an even better solution would use filter_map, so you don't have to unwrap at all), but it doesn't change what I said before. What if keys is something like &[1, 1]. Then the output would have to be two mutable references to whatever self.bar[1] is. We can't have that, so what do you want instead?SCappella
@SCappella You are absolutely right! I was able to get it working with your help. Thank you!user24182

1 Answers

1
votes

The following works by filtering the values using the provided keys, rather than mapping the keys to their values. The code in the question, as SCappella correctly stated, would have lead to undefined behavior if duplicate valid keys were provided.

Working code:

pub fn lookup(&mut self, keys: &[u32]) -> Vec<&mut String> {
   self.bar.iter_mut().filter_map(|(k, v)| {
         if keys.contains(k) {
             Some(v)
         } else {
             None
         }
   }).collect::<_>()
}