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 Normal
s. 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 impl
s, 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
T
must 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 impl
s, is to use a macro. Rust's standard library uses macros in many places to avoid repeating impl
s (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