93

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 How can an operator be overloaded for different RHS types and return values? 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.

Community
  • 1
  • 1
Jeremy Sorensen
  • 1,100
  • 1
  • 7
  • 14
  • 1
    "what if I wanted a 100 element struct" - Rust uses optimizations such as RVO that will automatically use a reference when appropriate and the better choice. – Shepmaster Jan 17 '15 at 23:47
  • @Shepmaster: RVO is only going to affect the return value, which I am returning by value. Can you point to any documentation that shows that traits for large structs should be implemented by value? – Jeremy Sorensen Jan 18 '15 at 00:26
  • 2
    The best documentation I know of would be the [book chapter on returning pointers](http://doc.rust-lang.org/book/pointers.html#returning-pointers). However, I [created an example of adding a large struct](http://is.gd/25ITa7) and checked the generated LLVM (slightly cleaned): `(%struct.Big* sret, %struct.Big*, %struct.Big*)`. I don't claim to be an LLVM expert, but that looks like it automatically is taking and returning by reference. – Shepmaster Jan 18 '15 at 00:41
  • 2
    The documentation is also referring to the return value, which I agree shouldn't be a ref. In fact that documentation used to say that you should not use pointers for input parameters unless you needed to, but that was actually removed. Also I changed your example to do pass by reference and found it removes two allocations (`%arg7 = alloca %struct.Big, align 8` and `%arg8 = alloca %struct.Big, align 8`) so it looks like for large structs at least, references are better. – Jeremy Sorensen Jan 18 '15 at 03:40
  • 2
    I should point out that I know less than anyone about LLVM, so my interpretation may be all wet. Also a distinct disadvantage of using references for operator overloading is that if you happen to not have references, `let c = (&a) + (&b);` is pretty annoying. – Jeremy Sorensen Jan 18 '15 at 03:43

2 Answers2

109

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.

Francis Gagné
  • 60,274
  • 7
  • 180
  • 155
  • 4
    "Add::add always takes self by value. Here, self is of type &'a Vector, because that's the type we're implementing Add on." That is the key information, that the type of self changes depending on if the trait is for a reference or not. Thanks! – Jeremy Sorensen Jan 17 '15 at 23:05
  • 12
    Wow. Amazed that this is the right answer, and yet it is. This all feels quite counter-intuitive. That you can define Add in two different ways depending on if its a reference or not feels like a recipe for trouble. – Squirrel Feb 10 '17 at 22:16
  • 1
    To bounce on @Squirrel 's comment, should we implement one operation from the other to avoid redundancy? Either the Add by reference copies the values and applies Add by value, or Add by value applies Add by reference on the moved values? – hsandt Jul 06 '19 at 17:59
  • 1
    Yes, absolutely! Code reuse is always good! :) Usually, you'll want the by-value implementation to be the main one if values are cheap to clone/copy and the by-reference implementation otherwise. – Francis Gagné Jul 07 '19 at 03:16
  • 2
    you actually even need to implement all the combinations if one is ref, the other not, and vice-versa. In rust proper it was done through a macro... https://stackoverflow.com/questions/38811387/how-to-implement-idiomatic-operator-overloading-for-values-and-references-in-rus – Emmanuel Touzery Jul 13 '19 at 18:01
  • the comment on the macro is `implements binary operators "&T op U", "T op &U", "&T op &U" based on "T op U"` – Emmanuel Touzery Jul 13 '19 at 18:03
  • Also see https://stackoverflow.com/a/57294171/430083. – Danylo Mysak Feb 12 '22 at 19:17
  • If one is using `derive_more` to implement `Add` on `Vector`, is there a way to coax it into implementing it for `&Vector` also? – davidA Apr 11 '23 at 02:03
  • Are two lifetimes necessary here? [I managed to get something similar working](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=3402c8b56c2f99e9ed02258fc594d9ac) with just one lifetime `'a` and I am wondering if I am missing something. – Momchil Atanasov Apr 19 '23 at 21:30
  • 1
    @MomchilAtanasov Indeed, one lifetime is probably likely enough. That's because `&'a T` is [covariant](https://doc.rust-lang.org/reference/subtyping.html#variance) in `'a` and the compiler is smart enough to coerce the arguments to a common type (with a shorter lifetime) if the arguments' lifetimes don't match. – Francis Gagné Apr 20 '23 at 06:46
25

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.

Emmanuel Touzery
  • 9,008
  • 3
  • 65
  • 81
  • looks like quite an outdated crate : o is there a similar crate nowadays? – David 天宇 Wong Jun 05 '23 at 21:47
  • 1
    good point. I see on their github page: "NOTICE: This crate is stable, but won't be receiving new features. For a more up-to-date version of this crate see auto_ops.": https://github.com/carbotaniuman/auto_ops. That one also wasn't updated very recently, but after all, there's such a thing as a "finished" library. – Emmanuel Touzery Jun 06 '23 at 06:59