Struct + From trait vs "struct" trait for code reuse

Hi, I'm learning rust and I would like to get some help regarding the code design.

I'm writing a simple "controller" and I would like to split the IO and core logic operations so that I could reuse the core logic for different input formats. What is a preferred way of doing it?

This is a bit vague and so bellow is a simple example. The question is whether ControllerInput should be a struct or a trait in order to integrate it with custom ServiceInput.

First option: input as a struct + From trait

struct ControllerInput {
    x: f32,
}

struct Controller {}
impl Controller {
    fn compute(&self, input: ControllerInput) {
        todo!();
    }
}

struct ServiceInput {
    y: f32,
}
impl From<ServiceInput> for ControllerInput {
    fn from(value: ServiceInput) -> Self {
        Self { x: value.y }
    }
}

Second option: input as a trait

trait ControllerInput {
    fn x(&self) -> f32;
}
struct Controller<T> {
    data: std::marker::PhantomData<T>,
}

impl<T: ControllerInput> Controller<T> {
    fn compute(&self, input: T) {
        todo!()
    }
}

struct ServiceInput {
    y: f32,
}
impl ControllerInput for ServiceInput {
    fn x(&self) -> f32 {
        self.y
    }
}

Which option is more idiomatic? What are the advanteges and disadvantages of both options? Are there any other alternatives?

1 Like

If you don't have a need for multiple different implementations of your trait to be used in Controller, there's really no reason to use a trait.


Also if you find yourself needing a PhantomData for a type parameter that's usually a good reason to stop and reconsider a design. It's not wrong necessarily but it can create problems with type inference, for example.

In this case the type parameter probably belongs on the method instead of the type

struct Controller;

impl Controller {
    fn compute<T: ControllerInput>(&self, input: T) {
        todo!()
    }
}
1 Like

The first option makes more sense to me.

Thank you! I put the phantom data field only to stop compiler complaining about "unused parameter". It didn't occur to me that just the generic method is good enough.

The idea is that there would be one controller library used in several different executables with ServiceInput1, ServiceInput2 etc. These executables would be in other crates, workspaces or even projects, and all of them would use the same Controller logic.

The first option I mentioned seem to more idiomatic. However (, I know it is a premature optimization), I was just wondering whether there's any cost incurred in using From trait or, on the other hand, if compute is a generic function.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.