Struct members who are traits that use associated

2019-08-25 06:22发布

问题:

I have a follow up question to this question: Expose a HashMap in a generic way that disregards the HashMap value

Suppose I want to use HashMapContainer (the same one that was defined in the previous question's first answer) as a member in another struct (let's call it MyDB) and in MyDB constructor I want to decide whether to construct this member as HashMapContainerImpl1 or HashMapContainerImpl2. I don't want to define MyDB as a template (e.g MyDB<T>) because MyDB users don't care about the value of the HashMap (MyDB constructor will decide about that). What is the right way to implement that?

Here is a sample code of what I want to achieve (it doesn't compile):

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>;
}

struct MyDB {
    hash_container: HashMapContainer
}

impl MyDB {
    pub fn new(hash_value_type: &str) -> MyDB {
        // have a logic to set hash_container to either 
        // HashMapContainerImpl1 or HashMapContainerImpl2
        // according to hash_value_type
    }

    pub fn count_keys(&self) -> usize {
        self.hash_container.get_hash_map().len()
    }
}

fn main() {
    let db = MyDB::new();
    println!("key count: {}", db.count_keys());
}

回答1:

tl;dr: this isn't possible.

First of all, this is not valid:

struct MyDB {
    hash_container: HashMapContainer
}

HashMapContainer is a trait, but you are trying to use it as a type. Instead, you would need to either (1) introduce a type parameter, constrained by the trait:

struct MyDB<H: HashMapContainer> {
    hash_container: H,
}

Or (2) use a trait object, for example in a Box:

struct MyDB {
    hash_container: Box<dyn HashMapContainer>,
}

Each of these approaches has different trade-offs. Using the type parameter will fix the type to something that must be known at compile time. The trait object would be more flexible because the concrete type can change at runtime, but has some performance implications as well as some restrictions on the trait and how it is used.

Since you want to pick the implementation of HashMapContainer at runtime, based on a string value, you have to go with the trait object route. However, since the concrete type is only known at runtime, the associated type would only be known at runtime too. This means compiler won't be able to type-check anything involving the associated type.

Essentially, your combined requirements; dynamically changing the trait implementation and relying on the trait's associated type; are incompatible.

If you could fix the associated type, so it was always the same, then this could work:

struct MyDB {
    hash_container: Box<dyn HashMapContainer<Value = SomeType>>,
}

Alternatively, if you are willing to limit implementations of the trait to a fixed set of known types, then you can encode them in an enum.

The actual answer here will depend on your real-world requirements and where you are able to bend them.