Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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:

  • #[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

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

    }
}
like image 982
Kyle Carow Avatar asked Nov 16 '25 06:11

Kyle Carow


1 Answers

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,
}
like image 169
cdhowie Avatar answered Nov 18 '25 19:11

cdhowie