1

I want to create a hash map from a vector of entities. I want the key to be a reference to a field in the corresponding value. This is something I have come up with.

struct Entity {
    id: String,
    patterns: Vec<Pattern>,
}

struct Package<'ent> {
    entity_map: HashMap<&'ent String, Entity>,
}

impl<'ent> Package<'ent> {
    fn from(entities: Vec<Entity>) -> Self {
        let entity_map: HashMap<&String, Entity> =
            entities.into_iter().map(|e| (&e.id, e)).collect();
        Package { entity_map }
    }
}

Of course, this isn't working. I am pretty new to Rust. Is there anything wrong with this approach? How can I achieve what I want? Any help will be appreciated.

Herohtar
  • 5,347
  • 4
  • 31
  • 41
souchatt
  • 11
  • 1
  • 1
    See the above-linked duplicate. Also, consider this: borrowing from a value in the map requires borrowing the map itself. But you need a mutable borrow to add more items to the map, so _even if lifetimes let you do this,_ as soon as you added the first element, the map would be frozen as it borrows itself and you would be unable to add a second element. If you want part of the value to be the key, you might want to use `HashSet` instead. – cdhowie May 07 '22 at 04:48

1 Answers1

0

In this case I think your best bet is to have the HashMap's keys and values both be references to elements in the original Vec, which should itself be passed by reference (as a slice, the more general version of &Vec) instead of by value.

use std::collections::HashMap;

struct Pattern;

struct Entity {
    id: String,
    patterns: Vec<Pattern>,
}

struct Package<'ent> {
    // Now the values are references too
    entity_map: HashMap<&'ent String, &'ent Entity>,
}

impl<'ent> Package<'ent> {
    // Package<'ent> can be constructed from any slice that outlives it
    fn from(entities: &'ent [Entity]) -> Self {
        let entity_map = entities
            .iter()
            .map(|e| (&e.id, e))
            .collect::<HashMap<_, _>>();
        Package { entity_map }
    }
}

As an aside, if you're implementing the function from(T), you probably want to do so as part of the implementation of the trait From<T> for your type.


If you can't store the vector for long enough to make the above solution work, then you can simply split Entity into its key and value components, which can then be used as owned values in the HashMap.

// Same Pattern and Entity as above

struct EntityValue {
    patterns: Vec<Pattern>,
}

impl Package {
    fn from(entities: Vec<Entity>) -> Self {
        let entity_map = entities
            .into_iter()
            .map(|e| {
                let Entity { id, patterns } = e;
                (id, EntityValue { patterns })
            })
            .collect::<HashMap<_, _>>();
        Package { entity_map }
    }
}
BallpointBen
  • 9,406
  • 1
  • 32
  • 62
  • Thanks for the answer. It is an enlightening solution. But the original use-case was deserializing it from yaml (where it's serialized as a list), which I think, wouldn't fit this solution. – souchatt May 07 '22 at 05:02
  • If your problem is that the input vector only exists for the duration of deserialization, but not after, then yep that's a problem. You could do your deserialization in two steps, first into a vector and then use that vector (which would persist more or less for the duration of your program) to construct the Package. Another solution is to remove the `id` from `Entity` — then you could have owned strings as keys and owned Entities (sans `id`) as values in your HashMap. – BallpointBen May 07 '22 at 05:06
  • That can also be a solution, but I would like `id` to be owned by `Entity` since it would result into cleaner implementations for the other methods. I think I would follow @cdhowie's suggestion of using `HashSet`. I think [this answer](https://stackoverflow.com/a/45390990/19058767) fits my solution perfectly. But thank you for your help. Really appreciate it! – souchatt May 07 '22 at 05:15