As I understand it, rust is a "has a" and not a "is a" language (composition over inheritance). this makes Liskov substitutions slightly more complicated but not impossible using Traits. While I can use LSP, it appears to not be the idiomatic way of implementing type coercion in rust. I'm left confused of how to operate.
minimal example
Let's assume I have two structs
struct Real(i32);
struct Complex(Real, Real);
And a function project
which takes a Complex
and return a projection of the input.
#[derive(Clone, Copy)]
struct Real(i32);
struct Complex(Real, Real);
// we pass by reference because we need to be blazingly fast
fn project(c : &Complex) -> Complex {
Complex(c.0, Real(0))
}
fn main() {
let a = Complex(Real(1), Real(2));
let x = project(&a);
println!("{} + {}i", x.0.0, x.1.0)
}
To keep things simple, please assume we are the situation in which we benefit from passing Real by reference and project
should not be duplicated as multiple implementation from a trait for Real
and Complex
.
Assume we expect to also use project
on Real
s from time to time.
Making project
somewhat generic
My OOP instincts pushes me to make some supertype for Real
and Complex
, let's say the trait AsReal
#[derive(Clone, Copy)]
struct Real(i32);
struct Complex(Real, Real);
trait AsReal {
fn as_real(&self) -> Real;
}
impl AsReal for Real { fn as_real(&self) -> Real { *self } }
impl AsReal for Complex { fn as_real(&self) -> Real { self.0 } }
fn project (r : &impl AsReal) -> Complex {
Complex( r.as_real(), Real(0) )
}
fn main() {
let a = Real(1);
let b = Complex(Real(2), Real(3));
let x = project(&a);
let y = project(&b);
println!("{} + {}i", x.0.0, x.1.0);
println!("{} + {}i", y.0.0, y.1.0);
}
But apparently, the rusty way would be to use AsRef<Real>
instead
#[derive(Clone, Copy)]
struct Real(i32);
struct Complex(Real, Real);
fn project<U: AsRef <Real>>(r : U) -> Complex {
Complex ( *r.as_ref(), Real(0) )
}
impl AsRef<Real> for Complex {
fn as_ref(&self) -> &Real { &self.0 }
}
impl AsRef<Real> for Real {
fn as_ref(&self) -> &Real { self }
}
fn main() {
let a = Real(1);
let b = Complex(Real(2), Real(3));
let x = project(&a);
let y = project(&b);
println!("{} + {}i", x.0.0, x.1.0);
println!("{} + {}i", y.0.0, y.1.0);
}
Which leaves me unsatisfied : the prototype for project
became very wordy and hard to read. So much so it feels like the convenience of use for project is simply not worth it.
Furthermore, it means the function must opt-in for Complex into Real coercion and I dislike that notion as it feel like it pushes me to develop defensively and use AsRef<...>
all the time.
I don't feel like I have the full picture, what would be the idiomatic way to interact with rust for situation like this ?