I am writing a Rust crate where I have a struct whose field implements a trait, and I'd like to provide a feature where:
#[cfg(not(feature = "dyn"))]
: struct field is T
, statically dispatched (more performant but less flexible at runtime)#[cfg(feature = "dyn")]
: struct field is Box<dyn MyTrait>
, a dynamically dispatched trait object (runtime flexibility at small performance cost)trait MyTrait {}
#[cfg(not(feature = "dyn"))]
struct MyStruct<T: MyTrait> {
my_field: T
}
#[cfg(feature = "dyn")]
struct MyStruct {
my_field: Box<dyn MyTrait>
}
My issue is that every single impl
block needs to be duplicated in its entirety, when the changes should only affect a couple lines.
#[cfg(not(feature = "dyn"))]
impl<T: MyTrait> MyStruct<T> {
fn new(field: T) -> Self {
Self { field }
}
}
#[cfg(feature = "dyn")]
impl MyStruct {
fn new(field: impl MyTrait + 'static) -> Self {
Self { field: Box::new(field) }
}
}
Is there a more elegant way to do this? Macro black magic? Is this just a bad idea and not worth doing?
Per @cdhowie's response
This almost gets to successful compilation! However MyTrait
actually has method (that I stripped out to get a minimal example):
trait MyTrait: Sized {
fn calculate(&self, struct: &MyStruct<Self>);
}
the reference &MyStruct<Self>
is necessary as this method needs to access data for calculations.
Attempting to impl as suggested doesn't work... something about it makes breaks dyn compatibility
impl MyTrait for Box<dyn MyTrait> {
// ^^^^^^^^^^^^^^^^
// ERROR
// the trait `MyTrait` cannot be made into an object
If I instead try to implement the trait for Box (not sure that this would solve my problem either), this is the trouble:
impl<T: MyTrait> MyTrait for Box<T> {
fn calculate(
&self,
struct: &MyStruct<Self>,
) {
(**self).calculate(struct)
// ^^^^^^
// ERROR: mismatched types
// expected reference &MyStruct<T>
// found reference &MyStruct<Box<T>>
}
}
There's little reason to actually use conditional compilation to do this. Instead, just implement MyTrait
on Box<dyn MyTrait>
and then consumers can use MyStruct<Box<dyn MyTrait>>
if they want dynamic dispatch.
trait MyTrait {}
impl MyTrait for Box<dyn MyTrait> {}
You could also default T
to Box<dyn MyTrait>
, and then bare uses of MyStruct
will implicitly use dynamic dispatch:
struct MyStruct<T: MyTrait = Box<dyn MyTrait>> {
my_field: T,
}