(Rust) Is there a way to auto-borrow for overloaded operators?

Viewed 244

I am trying to implement a basic Polynomial type in Rust, with overloaded operators for the math operations. So far, I could manage to implement the basic operations like addition, subtraction, etc. by overloading the operators. The problem is, I would like to take operands by reference to avoid unnecessary copies. Ie. :

impl ops::Add for &Polynomial {
    type Output = Polynomial;
    fn add(self, other: Self) -> Polynomial {
        let mut result = self.clone();
        for (power, coeff) in other.coefficients.iter().enumerate() {
            result.add_coeff(power, *coeff);
        }
        result
    }
}
impl ops::Sub for &Polynomial {
    //  Implementation
    //  ...
}
impl ops::Mul for &Polynomial {
    //  Implementation
    //  ...
}
//  Etc.

This works as expected, however it forces the user to borrow the operands explicitly each time, which becomes less readable as the expressions become more complex :

let (quotient, remainder) = p2.div_rem(&p1);

assert_eq!(&(&quotient * &p1) + &remainder, p2);

//  Even worse example found on the internet.
//  Note that you need to explicitly borrow each and every intermediate result :
let result = &(&(&a * &b) + &(&c * &d)) / 2;

Instead of the last two lines, I think it would be much cleaner and readable if I could just write :

assert_eq!(quotient * p1 + remainder, p2);

let result = (a * b + c * d) / 2;

…and let the borrow happen « automatically » (just like it does when you call a method which has a &self as its first parameter on any value : it is automatically and implicitly borrowed without having to explicitly borrow it).

However I don’t know if it is possible to do, or how ?

2 Answers

AFAIK, no, there's no easy way of doing this because Rust references are always explicit, never implicit, due to both design choices (i.e. avoid the same design issues of C++ references, which are weird) and necessity (due to the borrow checker).

T, &T, &mut T, *const T, ... are all different types with different semantics, so allowing impl ops::Add for &Polynomial to also work for Polynomial would basically mean that impl ops::Add for &T would implement automatically ops::Add for T, which could lead to very weird and potentially unsafe outcomes, especially if the opposite is also true. And what about &mut? Would &mut T automatically decay to &, and with what implications on the lifetime of the borrow itself?

Also, implementing ops::Add on non-Copy value types is quite weird, due to the operator then causing its operands to be moved in - which is definitely not what people expect most of the time when they use +.

The standard approach is to define arithmetic operations for both references and values. This can be a bit tedious for the implementation (and typically involves macros to cut down on code duplication), but provides maximum usability for the API user.

The user can then write regular-looking code without the extra borrows, but can also choose to borrow when they want to hold on to the value for later use (and thus avoid the needless copy). For example, with the num_bigint crate, all of these work:

    assert!(BigUint::from(1u8) + BigUint::from(1u8) == BigUint::from(2u8));
    assert!(&BigUint::from(1u8) + &BigUint::from(1u8) == BigUint::from(2u8));
    assert!(BigUint::from(1u8) + &BigUint::from(1u8) == BigUint::from(2u8));
    assert!(&BigUint::from(1u8) + BigUint::from(1u8) == BigUint::from(2u8));
Related