I am implementing a quick geometry crate for practice, and I want to implement two structs, Vector and Normal (this is because standard vectors and normal vectors map through certain transformations differently). I've implemented the following trait:
trait Components {
fn new(x: f32, y: f32, z: f32) -> Self;
fn x(&self) -> f32;
fn y(&self) -> f32;
fn z(&self) -> f32;
}
I'd also like to be add two vectors together, as well as two normals, so I have blocks that look like this:
impl Add<Vector> for Vector {
type Output = Vector;
fn add(self, rhs: Vector) -> Vector {
Vector { vals: [
self.x() + rhs.x(),
self.y() + rhs.y(),
self.z() + rhs.z()] }
}
}
And almost the exact same impl for Normals. What I really want is to provide a default Add impl for every struct that implements Components, since typically, they all will add the same way (e.g. a third struct called Point will do the same thing). Is there a way of doing this besides writing out three identical implementations for Point, Vector, and Normal? Something that might look like this:
impl Add<Components> for Components {
type Output = Components;
fn add(self, rhs: Components) -> Components {
Components::new(
self.x() + rhs.x(),
self.y() + rhs.y(),
self.z() + rhs.z())
}
}
Where "Components" would automatically get replaced by the appropriate type. I suppose I could do it in a macro, but that seems a little hacky to me.
What are blanket implementations? Blanket implementations leverage Rust's ability to use generic parameters. They can be used to define shared behavior using traits. This is a great way to remove redundancy in code by reducing the need to repeat the code for different types with similar functionality.
Implementing a trait on a type is similar to implementing regular methods. The difference is that after impl , we put the trait name we want to implement, then use the for keyword, and then specify the name of the type we want to implement the trait for.
A trait in Rust is a group of methods that are defined for a particular type. Traits are an abstract definition of shared behavior amongst different types. So, in a way, traits are to Rust what interfaces are to Java or abstract classes are to C++. A trait method is able to access other methods within that trait.
In Rust, it is possible to define generic impls, but there are some important restrictions that result from the coherence rules. You'd like an impl that goes like this:
impl<T: Components> Add<T> for T {
type Output = T;
fn add(self, rhs: T) -> T {
T::new(
self.x() + rhs.x(),
self.y() + rhs.y(),
self.z() + rhs.z())
}
}
Unfortunately, this does not compile:
error: type parameter
Tmust be used as the type parameter for some local type (e.g.MyStruct<T>); only traits defined in the current crate can be implemented for a type parameter [E0210]
Why? Suppose your Components trait were public. Now, a type in another crate could implement the Components trait. That type might also try to implement the Add trait. Whose implementation of Add should win, your crate's or that other crate's? By Rust's current coherence rules, the other crate gets this privilege.
For now, the only option, besides repeating the impls, is to use a macro. Rust's standard library uses macros in many places to avoid repeating impls (especially for the primitive types), so you don't have to feel dirty! :P
At present, macros are the only way to do this. Coherence rules prevent multiple implementations that could overlap, so you can’t use a generic solution.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With