I have two hierarchies of objects: let's call them params and evaluators:
trait MyParamTrait { fn compute_param(&self) -> f64; }
trait MyEvalTrait { fn eval(&self, x: &impl MyParamTrait) -> f64; }
I want the eval()
method to accept any of my parameter types. I defined a method that accepts &impl MyParamTrait)
argument. The code looks as below:
trait MyParamTrait { fn compute_param(&self) -> f64; }
struct Param1;
impl MyParamTrait for Param1 { fn compute_param(&self) -> f64 { 2.0 } }
struct Param2;
impl MyParamTrait for Param2 { fn compute_param(&self) -> f64 { 4.0 } }
trait MyEvalTrait { fn eval(&self, x: &impl MyParamTrait) -> f64; }
struct Eval1;
impl MyEvalTrait for Eval1 { fn eval(&self, x: &impl MyParamTrait) -> f64 { x.compute_param() * 2.0 } }
pub fn main(){
let p1 = Param1{};
let _p2 = Param2{};
let e = Eval1{};
println!("{}", e.eval(&p1));
// so far, so good ... but I need to store MyObjects
let mut all_my_obj: Vec<Box<dyn MyEvalTrait>> = Vec::new();
all_my_obj.push(Box::new(e));
}
Unfortunately I also need to store objects that implement MyEvalTrait
in a vector, and this is the moment when problem starts. Compiler tells me:
error[E0038]: the trait `MyEvalTrait` cannot be made into an object
--> src/main.rs:27:25
|
27 | let mut all_my_obj: Vec<Box<dyn MyEvalTrait>> = Vec::new();
| ^^^^^^^^^^^^^^^^^^^^^^^^^ `MyEvalTrait` cannot be made into an object
|
note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
--> src/main.rs:10:24
|
10 | trait MyEvalTrait { fn eval(&self, x: &impl MyParamTrait) -> f64; }
| ----------- ^^^^ ...because method `eval` has generic type parameters
| |
| this trait cannot be made into an object...
= help: consider moving `eval` to another trait
I can understand the reason; MyEvalTrait
is abstract and it can't be box'ed. But how can I make this working? I found a solution with generic:
trait MyEvalTrait<P> { fn eval(&self, x: &P) -> f64; }
struct Eval1;
impl MyEvalTrait<Param1> for Eval1 { fn eval(&self, x: &Param1) -> f64 { x.compute_param() * 2.0 } }
pub fn main(){
let p1 = Param1{};
let _p2 = Param2{};
let e = Eval1{};
println!("{}", e.eval(&p1));
// so far, so good ... but I need to store MyObjects
let mut all_my_obj: Vec<Box<dyn MyEvalTrait<Param1>>> = Vec::new();
all_my_obj.push(Box::new(e));
}
and this works just fine. I'd like to avoid that however, because in reality I have a few methods in my MyEvalTrait
, say another_eval(y: &impl OtherParamTrait)
which requires parameter of another type OtherParamTrait
. When I'm using generics, the trait MyEvalTrait<P1, P2, P3 ...> depends on several generic parameters which looks clumsy and is difficult to follow.
I wonder if there is another solution.