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?