I want to be able to store multiple factories in a single hashmap in order to add them to it later (by plugins for example) and then get each one by key-name in application (this is a resource manager).
The problem is in generic of Fabric trait, fabrics can create fruits of different types but I need to specify something in here HashMap<String, Box<dyn Fabric>>
, for example HashMap<String, Box<Fabric<Apple>>>
or HashMap<String, Box<Fabric<T>>>
which is also not wery useful because as I said we can create really different fruits.
Also I guess there might be a problem in foo method, about borrowing content.
So how would you implement this "the rust way"?
use std::collections::HashMap;
trait Fruit {
fn get_name(&self) -> String;
}
trait Fabric<T: Fruit> {
fn build(&self) -> Box<T>;
}
struct Banana {}
impl Fruit for Banana {
fn get_name(&self) -> String { String::from("I'm banana") }
}
struct BananaFabric {}
impl Fabric<Banana> for BananaFabric {
fn build(&self) -> Box<Banana> {
Box::new(Banana {})
}
}
struct Apple {}
impl Fruit for Apple {
fn get_name(&self) -> String { String::from("I'm apple") }
}
struct AppleFabric {}
impl Fabric<Apple> for AppleFabric {
fn build(&self) -> Box<Apple> {
Box::new(Apple {})
}
}
struct C {
map: HashMap<String, Box<dyn Fabric>>,
}
impl C {
pub fn new() -> C {
C {
map: HashMap::new()
}
}
pub fn foo(&self, key: String) {
match self.map.get(&key) {
Some(&fabric) => {
let fruit = fabric.build();
println!("{}", fruit.get_name())
},
_ => println!("No fabric found")
}
}
}
fn main() {
let c = C::new();
c.foo(String::from("bar"));
}
I can think of two options:
Dynamic dispatch (trait objects):
trait Fabric {
fn build(&self) -> Box<dyn Fruit>;
}
[...]
impl Fabric for BananaFabric {
fn build(&self) -> Box<dyn Fruit> {
Box::new(Banana {})
}
}
Using an enum
:
enum Fruits {
Banana,
Apple
}
impl Fruit for Fruits {
fn get_name(&self) -> String {
match self {
Banana => String::from("I'm banana"),
Apple => String::from("I'm apple"),
_ => String::from("")
}
}
}
[...]
impl Fabric for BananaFabric {
fn build(&self) -> Box<Fruits> {
Box::new(Fruits::Banana)
}
}
In both cases the foo
method will look like:
pub fn foo(&self, key: String) {
match self.map.get(&key) {
Some(fabric) => {
let fruit = fabric.build();
println!("{}", fruit.get_name())
},
_ => println!("No fabric found")
}
}