Expose a HashMap in a generic way that disregards

2019-07-29 09:45发布

I have different structs which all contain a HashMap with String as the key, but with different value types. For example, one struct has a member of type HashMap<String, String>, the other will have a member of type HashMap<String, u8>, and so on.

I want to define a method that can access these HashMap members and do generic actions on them that don't involve the value. For example, I want to count the number of keys, remove a key, check if a key exists, etc. I'm not sure how to implement this behavior.

The best way I had in mind so far is to define a trait that has a method that exposes the HashMap and let each struct implement it. However I don't know how to write this trait and method in a way that "ignores" the value type. I tried using a wildcard (_) but it doesn't work. How do I implement this?

Here is my code (which doesn't compile):

use std::collections::HashMap;

pub trait HashMapContainer {
    fn get_hash_map(&self) -> HashMap<String, _>;
}

struct HashMapContainerImpl1 {
    map: HashMap<String, String>,
}

impl HashMapContainerImpl1 {
    pub fn new() -> HashMapContainerImpl1 {
        HashMapContainerImpl1 {
            map: HashMap::new(),
        }
    }

    fn internal_logic_on_map(&mut self) {
        //....
    }
}

impl HashMapContainer for HashMapContainerImpl1 {
    fn get_hash_map(&self) -> HashMap<String, _> {
        self.map
    }
}

struct HashMapContainerImpl2 {
    map: HashMap<String, u8>,
}

impl HashMapContainerImpl2 {
    pub fn new() -> HashMapContainerImpl2 {
        HashMapContainerImpl2 {
            map: HashMap::new(),
        }
    }

    fn internal_logic_on_map(&mut self) {
        //....
    }
}

impl HashMapContainer for HashMapContainerImpl2 {
    fn get_hash_map(&self) -> HashMap<String, _> {
        self.map
    }
}

fn do_generic_actions_on_map(hm_container: &HashMapContainer) {
    println!("key count: {}", hm_container.get_hash_map().len());
    println!(
        "key esists? {}",
        hm_container.get_hash_map().get("key1").is_some()
    );
    hm_container.get_hash_map().remove("key2");
}

fn main() {
    let cont1 = HashMapContainerImpl1::new();
    let cont2 = HashMapContainerImpl2::new();
    do_generic_actions_on_map(cont1);
    do_generic_actions_on_map(cont2);
}

标签: hashmap rust
1条回答
forever°为你锁心
2楼-- · 2019-07-29 10:18

With an associated type

The code below using a generic is correct, but after thinking about it, I think that using an associated type might be more suitable here. The trait should look like this:

pub trait HashMapContainer {
    type Value;
    fn get_hash_map(&self) -> &HashMap<String, Self::Value>;
    fn get_hash_map_mut(&mut self) -> &mut HashMap<String, Self::Value>;
}

The difference is, that you now can only implement the trait once for a struct and not multiple times, which is more correct in this case.

The implementations are roughly the same as with the generic type parameter.

impl HashMapContainer for HashMapContainerImpl1 {
    type Value = String;
    fn get_hash_map(&self) -> &HashMap<String, Self::Value> {
        &self.map
    }
    fn get_hash_map_mut(&mut self) -> &mut HashMap<String, Self::Value> {
        &mut self.map
    }
}

impl HashMapContainer for HashMapContainerImpl2 {
    type Value = u8;
    fn get_hash_map(&self) -> &HashMap<String, Self::Value> {
        &self.map
    }
    fn get_hash_map_mut(&mut self) -> &mut HashMap<String, Self::Value> {
        &mut self.map
    }
}

(Playground)

You can also look at When is it appropriate to use an associated type versus a generic type? which explains the difference between those two pretty good.

With a generic type

This is solvable by introducing a generic type in your HashMapContainer trait.

pub trait HashMapContainer<T> {
    fn get_hash_map(&self) -> &HashMap<String, T>;
    fn get_hash_map_mut(&mut self) -> &mut HashMap<String, T>;
}

I changed the signature to return a reference to the HashMap. It would be possible to do it without, e.g. by using clone or taking self as value, instead of as reference. I also introduced a _mut version.

The implementation is straight foreword:

impl HashMapContainer<String> for HashMapContainerImpl1 {
    fn get_hash_map(&self) -> &HashMap<String, String> {
        &self.map
    }
    fn get_hash_map_mut(&mut self) -> &mut HashMap<String, String> {
        &mut self.map
    }
}

impl HashMapContainer<u8> for HashMapContainerImpl2 {
    fn get_hash_map(&self) -> &HashMap<String, u8> {
        &self.map
    }
    fn get_hash_map_mut(&mut self) -> &mut HashMap<String, u8> {
        &mut self.map
    }
}
查看更多
登录 后发表回答