I think the answer is No. When trying to reply to @Eugine I decided to restart from the bottom up. I decided to just create the NaturalNumber and RealNumber without traits first, and then extract the common logic.
struct RealNumber(f64);
trait RealNumberOperation {
fn apply(&self, number: &mut RealNumber);
}
impl RealNumber {
fn apply_operation(&mut self, operation: impl RealNumberOperation) {
operation.apply(self);
}
}
struct AddOneReal;
impl RealNumberOperation for AddOneReal {
fn apply(&self, number: &mut RealNumber) {
// Implementation of the operation
println!("Adding one to a real number");
number.0 += 1.0;
}
}
struct NaturalNumber(u32);
impl NaturalNumber {
fn apply_operation(&mut self, operation: impl NaturalOperation) {
operation.apply(self);
}
}
trait NaturalOperation {
fn apply(&self, number: &mut NaturalNumber);
}
struct AddOne;
impl NaturalOperation for AddOne {
fn apply(&self, number: &mut NaturalNumber) {
// Implementation of the operation
println!("Adding one to a natural number");
number.0 += 1;
}
}
fn main() {
let mut real_number = RealNumber(5.0);
real_number.apply_operation(AddOneReal);
let mut natural_number = NaturalNumber(5);
natural_number.apply_operation(AddOne);
}
And then I wondered, can I create a trait that defines the apply_operation(&mut self, operation: ???)
. Sure, and then I can make the associated type that particular Operation per implementation. But then I lose information on which way that particular implementation stores its data (either u32
or f64
here), and while the signature works, I'm no longer able to determine how to access the data. That I could resolve with a getter and setter, but that needed more type annotations. And that made it dyn incompatible at some point. I'm not sure this is possible.