How do I implement the Add trait for a reference to a struct?

ReferenceRustTraitsLifetime

Reference Problem Overview


I made a two element Vector struct and I want to overload the + operator.

I made all my functions and methods take references, rather than values, and I want the + operator to work the same way.

impl Add for Vector {
    fn add(&self, other: &Vector) -> Vector {
        Vector {
            x: self.x + other.x,
            y: self.y + other.y,
        }
    }
}

Depending on which variation I try, I either get lifetime problems or type mismatches. Specifically, the &self argument seems to not get treated as the right type.

I have seen examples with template arguments on impl as well as Add, but they just result in different errors.

I found https://stackoverflow.com/questions/24594374/overload-operators-with-different-rhs-type but the code in the answer doesn't work even if I put a use std::ops::Mul; at the top.

I am using rustc 1.0.0-nightly (ed530d7a3 2015-01-16 22:41:16 +0000)

I won't accept "you only have two fields, why use a reference" as an answer; what if I wanted a 100 element struct? I will accept an answer that demonstrates that even with a large struct I should be passing by value, if that is the case (I don't think it is, though.) I am interested in knowing a good rule of thumb for struct size and passing by value vs struct, but that is not the current question.

Reference Solutions


Solution 1 - Reference

You need to implement Add on &Vector rather than on Vector.

impl<'a, 'b> Add<&'b Vector> for &'a Vector {
    type Output = Vector;

    fn add(self, other: &'b Vector) -> Vector {
        Vector {
            x: self.x + other.x,
            y: self.y + other.y,
        }
    }
}

In its definition, Add::add always takes self by value. But references are types like any other1, so they can implement traits too. When a trait is implemented on a reference type, the type of self is a reference; the reference is passed by value. Normally, passing by value in Rust implies transferring ownership, but when references are passed by value, they're simply copied (or reborrowed/moved if it's a mutable reference), and that doesn't transfer ownership of the referent (because a reference doesn't own its referent in the first place). Considering all this, it makes sense for Add::add (and many other operators) to take self by value: if you need to take ownership of the operands, you can implement Add on structs/enums directly, and if you don't, you can implement Add on references.

Here, self is of type &'a Vector, because that's the type we're implementing Add on.

Note that I also specified the RHS type parameter with a different lifetime to emphasize the fact that the lifetimes of the two input parameters are unrelated.


1 Actually, reference types are special in that you can implement traits for references to types defined in your crate (i.e. if you're allowed to implement a trait for T, then you're also allowed to implement it for &T). &mut T and Box<T> have the same behavior, but that's not true in general for U<T> where U is not defined in the same crate.

Solution 2 - Reference

If you want to support all scenarios, you must support all the combinations:

  • &T op U
  • T op &U
  • &T op &U
  • T op U

In rust proper, this was done through an internal macro.

Luckily, there is a rust crate, impl_ops, that also offers a macro to write that boilerplate for us: the crate offers the impl_op_ex! macro, which generates all the combinations.

Here is their sample:

#[macro_use] extern crate impl_ops;
use std::ops;

impl_op_ex!(+ |a: &DonkeyKong, b: &DonkeyKong| -> i32 { a.bananas + b.bananas });

fn main() {
    let total_bananas = &DonkeyKong::new(2) + &DonkeyKong::new(4);
    assert_eq!(6, total_bananas);
    let total_bananas = &DonkeyKong::new(2) + DonkeyKong::new(4);
    assert_eq!(6, total_bananas);
    let total_bananas = DonkeyKong::new(2) + &DonkeyKong::new(4);
    assert_eq!(6, total_bananas);
    let total_bananas = DonkeyKong::new(2) + DonkeyKong::new(4);
    assert_eq!(6, total_bananas);
}

Even better, they have a impl_op_ex_commutative! that'll also generate the operators with the parameters reversed if your operator happens to be commutative.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJeremy SorensenView Question on Stackoverflow
Solution 1 - ReferenceFrancis GagnéView Answer on Stackoverflow
Solution 2 - ReferenceEmmanuel TouzeryView Answer on Stackoverflow