Implementing std::ops::Add on a generic trait

I have the following Parameter trait and struct definitions:

pub trait Parameter<T: Copy> {
    fn get(&self) -> T;
}

pub struct ParameterAdd<T: Add> {
    first_parameter: Box<dyn Parameter<T>>,
    second_parameter: Box<dyn Parameter<T>>,
}
impl<T: Add<Output = T> + Copy> Parameter<T> for ParameterAdd<T> {
    fn get(&self) -> T {
        return self.first_parameter.get() + self.second_parameter.get();
    }
}
impl<T: Add<Output = T> + Copy> Add for Box<dyn Parameter<T>> {
    type Output = ParameterAdd<T>;

    fn add(self, other: Self) -> ParameterAdd<T> {
        ParameterAdd{
            first_parameter: self,
            second_parameter: other,
        }
    }
}

And the following implementation of the Parameter trait:

pub struct Constant<T: Copy> {
    pub value: T,      
}
 
impl<T: Copy> Parameter<T> for Constant<T> {
    fn get(&self) -> T {
        self.value
    }
} 

With this, I expect to be able to add together any objects which implement Parameter<T: Add<Output = T>> however, trying to run the following test:

#[test]
fn parameter_addition() {
    let c1: Box<Constant<i64>> = Box::new(Constant{value: 7});
    let c2: Box<Constant<i64>> = Box::new(Constant{value: 3});
    assert_eq!(10, (c1.add(c2)).get());
}

Gives the following error:

   Compiling walnut v0.1.0 (/home/armand/Documents/art/walnut)
error[E0599]: no method named `add` found for struct `Box<Constant<i64>>` in the current scope
 --> tests/parameter_test.rs:7:24
  |
7 |     assert_eq!(10, (c1.add(c2)).get());
  |                        ^^^ method not found in `Box<Constant<i64>>`

In the first block of code, I believe I have implemented Add for Box<dyn Parameter<T: <Output = T>> which should cover Box<Constant<i64>> but, it evidently doesn't. How do I go about implementing the Addition operator on Parameter<T> correctly?

Just help the compiler by specifying the type explicitly:

let c1: Box<dyn Parameter<i64>> = Box::new(Constant{value: 7});
let c2: Box<dyn Parameter<i64>> = Box::new(Constant{value: 3});

Box<Constant<i64>> can be coerced (read: can be implicitly be converted) to a Box<dyn Parameter<i64>>, but is not a Box<dyn Parameter<i64>> itself.

Coercions however don't always happen implicitly. In particular according to the nomicon:

Note that we do not perform coercions when matching traits (except for receivers, ...)

Calling add will fall under this case, because it will try to make the type match the Add<_> trait

1 Like

It's possible there is some confusion here on how traits and dyn Trait work in Rust.

  • Traits (and dyn Trait) aren't classes; a trait itself is not a type either
  • dyn Trait is a concrete type, albeit
    • One with a dynamic size (aka "is unsized", does not implement Sized)
    • One that performs dynamic dispatch
    • One that Sized implementors of Trait can coerce to
    • But still a singular, distinct, concrete type
  • Implementors of Trait are not subtypes of dyn Trait
  • Box<Implementor> is not a subtype of Box<dyn Trait> either, etc.
  • (It didn't come up here but) dyn Trait has an implementation of Trait supplied by the compiler, but Box<dyn Trait> and &dyn Trait (etc) do not. You can supply your own though.

So, in case it wasn't clear, when you implement something for dyn Trait or Box<dyn Trait>, you're not implementing it for every implementor-of-Trait. The coercion from implementor-of-Trait to dyn Trait has to happen (and like the others said, it's not always automatic).

2 Likes

Thank you this worked perfectly!

Fantastic thank you, this was helpful in understanding the distinction