Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Providing Blanket Trait Implementations for a Custom Trait

Tags:

rust

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.

like image 264
anjruu Avatar asked Sep 07 '15 03:09

anjruu


People also ask

What are blanket implementations?

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.

How do you implement traits?

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.

What is a trait in Rust programming?

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.


2 Answers

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 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 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

like image 157
Francis Gagné Avatar answered Nov 15 '22 10:11

Francis Gagné


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.

like image 44
Chris Morgan Avatar answered Nov 15 '22 10:11

Chris Morgan