Reputation: 513
My problem: I have a function that returns a Box<dyn Trait_1>
-object. Let's say I have N=2
possible types at level 1 that implement this Trait_1
and I want both of these types accessible via a match statement. But now these 2 possible types are each generic as well (but have to implement Trait_2
) and I have M=2
possible types at level 2 that I want to use. Now I can either have one match with 4 arms or 2 nested matches with 2 arms each. I have to write 4 statements that initialize this Box
. This is very simple. But if I had N=15
types at level 1 (that impl Trait_1) and 20 types at level 2 that impl some other Trait_2
I'll end up writing 300 different initializations! That's a lot of work and also I will probably put in some errors.
I've written up a simple example of my problem with 2 nested match statements with each having 2 arms resulting in a total of 4 initializations (I've omitted the Trait-implementations)
pub trait Trait_1 {}
pub trait Trait_2 {}
pub struct SingleContainer<T: Trait_2> {
item: T,
}
pub struct DoubleContainer<T: Trait_2> {
item_1: T,
item_2: T,
}
// -> impl Trait_1 for SingleContainer and DoubleContainer
struct X {
number: f64,
}
struct Y {
number: u32,
}
// -> impl Trait_2 for X and Y
fn main() {
let a = // determined at runtime
let b = // determined at runtime
let thing = give_thing(a, b);
}
fn give_thing(thing_number: usize, thing_name: &str) -> Box<dyn Trait_1> {
match thing_number {
1 => match thing_name {
"X" => Box::new(SingleContainer::<X>::default()),
"Y" => Box::new(SingleContainer::<Y>::default()),
_ => todo!(),
},
2 => match thing_name {
"X" => Box::new(DoubleContainer::<X>::default()),
"Y" => Box::new(DoubleContainer::<Y>::default()),
_ => todo!(),
},
_ => todo!(),
}
}
Is there a better way to do this? I have difficulties seeing another way, as each of these things is a different type. I want that at compile time each of the possible combinations of (SingleContainer, DoubleContainer) and (X,Y) "exists" and therefor will be optimized. If there's a better way to achieve this please let me know! I basically only need a function that returns a Box<dyn MyTrait>
-object that gets 2 arguments. But as each combination of arguments returns a different type I'm very confused!
Edit: Would blanket implementations help in this case in any way?
(If someone has a better way of phrasing my question, feel free to correct me!)
Upvotes: 1
Views: 69
Reputation: 71545
A little type metaprogramming can help here.
trait MakeTrait1 {
type Make<T: Trait_2 + Default + 'static>: Trait_1 + Default + 'static;
}
struct MakeSingleContainer;
impl MakeTrait1 for MakeSingleContainer {
type Make<T: Trait_2 + Default + 'static> = SingleContainer<T>;
}
struct MakeDoubleContainer;
impl MakeTrait1 for MakeDoubleContainer {
type Make<T: Trait_2 + Default + 'static> = DoubleContainer<T>;
}
fn make_trait1<T: MakeTrait1>(thing_name: &str) -> Box<dyn Trait_1> {
match thing_name {
"X" => Box::new(T::Make::<X>::default()),
"Y" => Box::new(T::Make::<Y>::default()),
_ => todo!(),
}
}
fn give_thing(thing_number: usize, thing_name: &str) -> Box<dyn Trait_1> {
match thing_number {
1 => make_trait1::<MakeSingleContainer>(thing_name),
2 => make_trait1::<MakeDoubleContainer>(thing_name),
_ => todo!(),
}
}
We basically create a type factory that for the inner type creates the outer type. Then we have a function that makes a runtime decision based on this type-level factory, and all left to do is to have a function that calls this function with each factory.
Upvotes: 1