rustconditional-compilationdynamic-dispatch

Conditional compilation affecting type parameter


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:

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

Edit

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>>

    }
}

Solution

  • 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,
    }