I've seen a number of posts on StackOverflow (and perhaps here) dedicated to the trait cannot be made into an object... error for traits using generics.
Simple example:
use num::{Complex, Zero, One};
trait HasNumber {
fn favorite<T: Zero + One> (&self) -> T;
}
struct Has1 {}
impl HasNumber for Has1 {
fn favorite<T: Zero + One> (&self) -> T { T::zero() }
}
struct Has2 {}
impl HasNumber for Has2 {
fn favorite<T: Zero + One> (&self) -> T { T::one() }
}
fn main(){
let h1 = Has1{};
h1.favorite::<f64>();
h1.favorite::<Complex<f64>>();
let h2 = Has2{};
h2.favorite::<f64>();
h2.favorite::<Complex<f64>>();
let v: Vec<Box <dyn HasNumber>> = vec![ // <= problem's here
Box::new(Has1{}),
Box::new(Has2{}),
];
}
Errors:
|
4 | trait HasNumber {
| --------- this trait cannot be made into an object...
5 | fn favorite<T: Zero + One> (&self) -> T;
Playground:
My question: what is the idiomatic Rust version of this combination (generics + "dynamic" dispatch)?
Of course in my non-simplified-reality there are far more than two things like Has1 and Has2. And they have aways more elaborate behavior (hence the desired for Box<dyn>).
The root problem is that a dyn Trait has to know all of the methods it provides ahead of time in order to create the functions that can be called dynamically via virtual dispatch.
If you have generic parameters, then there are an infinite number of methods that need to be provided (and a set that can't possibly be known ahead of time; downstream crates can create new types and monomorphized for them).
The solution is to degenericize the methods with further virtualization. You can keep the nicer monomorphized API for known types by gating them with where Self: Sized (but this won't work for dyn of course).
For inputs, this is rather simple. Just take input as &dyn Trait or Box<dyn Trait> or whatever container as needed.
For outputs, you need to use out parameters. And if you need to use associated functions (like T::one(), rather than a method like x.one()), it becomes even more convoluted.
Thankfully, you can hide some of the complexity via inherent impls on the dyn Trait type itself, which are allowed to be generic and monomorphic.
So a final solution (untested) might look something like
enum-dispatch might be worth a look. It makes it much easier to define and work with enums that contain variants for structs that implement some trait. Assuming that works for you, you can then just have a Vec of that enum instead of using trait objects.