Rust - create hashmap which uses part of the data it's storing from an iterator

Viewed 655

I'm new to rust and am trying to figure out how to create a HashMap of borrowed values from a Vec of data but when I try to do it I get a Vec into a HashMap the ownership model fights me. I don't know how to accomplish this, maybe I'm just trying something that is against the Rust mentality.

For Example:

struct Data{
    id: String,
    other_value: String,
}

//inside a method somewhere
let data_array = load_data(); // returns a Vec<Data>
let mut hash = HashMap::new(); // HashMap<&String, &Data>

for item in data_array {
    hash.insert(&item.id, &item);
}

As far As I know there should be a way to populate this data in this way as the HashMap would be storing references to the original data. Or maybe I've just flat out misunderstood the docs... ¯_(ツ)_/¯

3 Answers

There key issue here is that you are consuming the Vec. for loops in Rust work over things that implement IntoIter. IntoIter moves the Vec into an iterator - the Vec itself no longer exists once this is done.

Therefore the items that you are looping though disappear at the end of each iteration., so those references end up referencing nonexistent data (dangling references). If you tried to using them, Bad Things Would Happen. Rust prevents you shooting yourself in the foot like that, so you get an error telling you that the reference does not live long enough. The solution to make your code compile is very easy. Just add .iter() to the end of the loop, which will iterate through references rather than consume the Vec.

for item in data_array.iter() {
    hash.insert(&item.id, item); //Note we don't need an `&` in front of item
}

So turns out you can borrow the iterator value by borrowing the collection (Vec). So the example above turns into:

for item in &data_array {
    hash.insert(&item.id, item);
}

Notice the &data_array which turns item from Data type to &Data and allows you to use the borrowed value.

I'm still relatively new to Rust, so this might not be right, but I think it does what you want, but once and for all -- i.e. a function that makes it easy to turn a collection into a map using a closure to generate the keys:

fn map_by<I,K,V>(iterable: I, f: impl Fn(&V) -> K) -> HashMap<K,V>
where I: IntoIterator<Item = V>,
      K: Eq + Hash
{
    iterable.into_iter().map(|v| (f(&v), v)).collect()
}

Allowing you to say

 map_by(data_array.iter(), |item| &item.id)

Here it is in the playground:

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=87c0e4d1e68ccb6dd3f2c43ac9f318c7

Please nudge me in the right direction if I have this wrong.

Is there a function like this lying around in std?

Related