14

I am trying to create a base trait that will implement other operator traits (Add, Subtract, Multiply, Divide, etc...) for me.

This fails to compile, it looks like an issued with Sized, but even when Measurement is set to require Sized it does not work. Is this even possible?

use std::ops::Add;

#[derive(Copy, Clone, Debug)]
struct Unit {
    value: f64,
}

impl Unit {
    fn new(value: f64) -> Unit {
        Unit { value: value }
    }
}

trait Measurement: Sized {
    fn get_value(&self) -> f64;
    fn from_value(value: f64) -> Self;
}

impl Measurement for Unit {
    fn get_value(&self) -> f64 {
        self.value
    }
    fn from_value(value: f64) -> Self {
        Unit::new(value)
    }
}

// This explicit implementation works
/*
impl Add for Unit {
    type Output = Unit;

    fn add(self, rhs: Unit) -> Unit {
        let a = self.get_value();
        let b = rhs.get_value();
        Unit::from_value(a + b)
    }
}
*/

// This trait implementation does not
impl Add for Measurement {
    type Output = Self;

    fn add(self, rhs: Self) -> Self {
        let a = self.get_value();
        let b = rhs.get_value();
        Self::from_value(a + b)
    }
}

fn main() {
    let a = Unit::new(1.5);
    let b = Unit::new(2.0);
    let c = a + b;

    println!("{}", c.get_value());
}

(playground)

error[E0277]: the trait bound `Measurement + 'static: std::marker::Sized` is not satisfied
  --> src/main.rs:42:6
   |
42 | impl Add for Measurement {
   |      ^^^ `Measurement + 'static` does not have a constant size known at compile-time
   |
   = help: the trait `std::marker::Sized` is not implemented for `Measurement + 'static`

error[E0038]: the trait `Measurement` cannot be made into an object
  --> src/main.rs:42:6
   |
42 | impl Add for Measurement {
   |      ^^^ the trait `Measurement` cannot be made into an object
   |
   = note: the trait cannot require that `Self : Sized`

error[E0038]: the trait `Measurement` cannot be made into an object
  --> src/main.rs:43:5
   |
43 |     type Output = Self;
   |     ^^^^^^^^^^^^^^^^^^^ the trait `Measurement` cannot be made into an object
   |
   = note: the trait cannot require that `Self : Sized`

error[E0038]: the trait `Measurement` cannot be made into an object
  --> src/main.rs:45:5
   |
45 |     fn add(self, rhs: Self) -> Self {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Measurement` cannot be made into an object
   |
   = note: the trait cannot require that `Self : Sized`
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
jocull
  • 20,008
  • 22
  • 105
  • 149

3 Answers3

12

The issue is not with Sized. The syntax you're looking for is:

impl<T: Measurement> Add for T { ... }

instead of:

impl Add for Measurement { ... }

Because the right-hand side of the for must be an object, not a trait, however a type parameter constrained to a trait (i.e. a T required to be Measurement) is valid.


Now your code still won't compile. You will get the following:

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]

The issue here is of a totally different kind. I'm not sure it's related to the question any more but I'll still explain what's going on. When you write an impl for Add to any T which is Measurement, you open the possibility that a type would already implement Add on its own, and would also implement Measurement elsewhere. Imagine if you wanted to implement Measurement on u8 (which is silly but possible): which impl should Rust choose for Add? The original std impl or your Measurement impl? (in-depth discussion about this issue)

Right now Rust plainly forbids an impl if it is not at least 1) your own trait or 2) your own type (where "own" formally means, in the crate you're writing your impl). This is why you can write impl Add for Unit: because you own Unit.

The easiest solution would be to give up and implement Add independently for each type you're planning to make Unit. Say your crate defines Inches and Centimeter, each one would have its own Add impl. If the code is insultingly similar, and you feel you broke DRY, leverage macros. Here is how the std crate does it:

macro_rules! add_impl {
    ($($t:ty)*) => ($(
        #[stable(feature = "rust1", since = "1.0.0")]
        impl Add for $t {
            type Output = $t;

            #[inline]
            fn add(self, other: $t) -> $t { self + other }
        }

        forward_ref_binop! { impl Add, add for $t, $t }
    )*)
}
mdup
  • 7,889
  • 3
  • 32
  • 34
  • This is the same place I was just arriving at. The `T: Measurement` syntax would be so nice, but I understand. Thanks for the link to macros, and if I'm able to make a macro and share it via modules that would be best. – jocull Jun 26 '15 at 21:54
3

You cannot implement a trait for a trait, you implement a trait only for types. But you can implement a trait for a generic type that implement a certain traits (trait bounds). Something like this:

impl<T : Measurement> Add<T> for T {
    type Output = T;

    fn add(self, rhs: Self) -> T {
        let a = self.get_value();
        let b = rhs.get_value();
        T::from_value(a + b)
    }
}

Unfortunately you can do this only for traits defined in your crate (its called coherence), so you cannot do that for the std Add trait because it's defined in the std crate, not in yours.

I think you might need to define some macros to do what you want to do.

eulerdisk
  • 4,299
  • 1
  • 22
  • 21
1

Here's a working version with macros, as suggested:

use std::ops::Add;

#[derive(Copy, Clone, Debug)]
struct Unit {
    value: f64,
}

impl Unit {
    fn new(value: f64) -> Unit {
        Unit { value: value }
    }
}

trait Measurement: Sized {
    fn get_value(&self) -> f64;
    fn from_value(value: f64) -> Self;
}

impl Measurement for Unit {
    fn get_value(&self) -> f64 {
        self.value
    }
    fn from_value(value: f64) -> Self {
        Unit::new(value)
    }
}

macro_rules! add_impl {
    ($($t:ty)*) => ($(
        impl Add for $t {
            type Output = $t;

            fn add(self, other: $t) -> $t {
                let a = self.get_value();
                let b = other.get_value();
                let r = a + b;
                Self::from_value(r)
            }
        }
    )*)
}

add_impl! { Unit }

fn main() {
    let a = Unit::new(1.5);
    let b = Unit::new(2.0);
    let c = a + b;

    println!("{}", c.get_value());
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366