简体   繁体   中英

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

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.

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 other 1 , 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 (ie 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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM