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?
Rust playground link
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,
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With