Design of a piece of code that extracts routines with a common interface from a trait

I'm interested in whether there's a better design for a piece of code that extracts common subroutines implemented by a user within a trait. Essentially, I would like to have the user implement a trait that defines a number of functions with a common interface. Then, I would like to take these functions and pass them to another function that operates on this common interface. Generally speaking, I'd like the user to implement a single trait in order to minimize confusion. In addition, these routines often operate on cached information shared between the functions, so it make some sense to define them together.

Anyway, I have a solution for this, but it seems someone over engineered, so I'm curious if there's a better way. The code as it stands now looks like:

// Trait that the user must define
trait FunctionWithGen {
    fn gen(&mut self, x: f64) -> &Self;
    fn f_eval(&self, y: f64) -> f64;
    fn f_desc(&self) -> String;
    fn g_eval(&self, y: f64) -> f64;
    fn g_desc(&self) -> String;
}

// Trait that both f and g embody
trait FunctionSmall {
    fn eval(&self, y: f64) -> f64;
    fn desc(&self) -> String;
}

// Extracts f
struct F<'a, Data>(&'a Data)
where
    Data: FunctionWithGen;
impl<'a,Data> FunctionSmall for F<'a,Data>
where
    Data : FunctionWithGen
{
    fn eval(&self, y: f64) -> f64 {
        self.0.f_eval(y)
    }
    fn desc(&self) -> String {
        self.0.f_desc()
    }
}

// Extracts g
struct G<'a, Data>(&'a Data)
where
    Data: FunctionWithGen;
impl<'a,Data> FunctionSmall for G<'a,Data>
where
    Data : FunctionWithGen
{
    fn eval(&self, y: f64) -> f64 {
        self.0.g_eval(y)
    }
    fn desc(&self) -> String {
        self.0.g_desc()
    }
}

// Function that operates on one of the encapsulated functions
fn foo(h: &dyn FunctionSmall) {
    println!("{} : {}", h.desc(), h.eval(3.));
}

// Create some user defined data
struct Data {
    a: f64,
    b: f64,
    cached: f64,
}

// Implement the functions on this data
impl FunctionWithGen for Data {
    fn gen(&mut self, x: f64) -> &Self {
        self.cached = self.a.powi(2) * x + self.b * x.powi(2);
        self
    }
    fn f_eval(&self, y: f64) -> f64 {
        self.cached * 2. * y
    }
    fn f_desc(&self) -> String {
        "Hello, I am f".to_string()
    }
    fn g_eval(&self, y: f64) -> f64 {
        self.cached * 4. * y
    }
    fn g_desc(&self) -> String {
        "Hello, I am g".to_string()
    }
}

// Test the design
fn main() {

    // Create some data
    let mut data = Data {
        a: 1.2,
        b: 2.3,
        cached : 0.,
    };

    // Cache the data
    data.gen(3.4);

    // Run the functions through foo
    foo(&F(&data));
    foo(&G(&data));
}

This produces the desired result:

Hello, I am f : 188.90399999999997
Hello, I am g : 377.80799999999994

Is there a better design to accomplish this?

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.