Is there a way to implement Add for non-Copy-able types so that resulting usage doesn't need references?

Viewed 47

I am trying to implement overloaded arithmetic operations for a struct D<T>:

struct D<T> {
    reg: f64,
    deriv: T,
}

If T: Copy like another f64 everything is fine, I just implement the Add trait straightforwardly.

However I'd like to have T be a type that is not (necessarily) copyable. E.g. it may be some recursively defined enum.

I can't seem to find a good way to implement Add now without getting into trouble at usage - in particular ideally the D<T> values are used exactly like f64 could be used. I tried implementing Add for all combinations of D<T> and &D<T> (which by itself felt pretty cumbersome, but would be ok if it worked):

impl<T> Add for D<T>
where
    T: Clone + Add<T, Output = T>,
{
    type Output = D<T>;

    fn add(self, D { reg, deriv }: Self) -> Self::Output {
        D {
            reg: self.reg + reg,
            deriv: self.deriv + deriv,
        }
    }
}

impl<T> Add for &D<T>
where
    T: Clone + Add<T, Output = T>,
{
    type Output = D<T>;

    fn add(self, D { reg, deriv }: Self) -> Self::Output {
        D {
            reg: self.reg + reg,
            deriv: self.deriv.clone() + deriv.clone(),
        }
    }
}

impl<T> Add<D<T>> for &D<T>
where
    T: Clone + Add<T, Output = T>,
{
    type Output = D<T>;

    fn add(self, D { reg, deriv }: D<T>) -> Self::Output {
        D {
            reg: self.reg + reg,
            deriv: self.deriv.clone() + deriv,
        }
    }
}

impl<T> Add<&D<T>> for D<T>
where
    T: Clone + Add<T, Output = T>,
{
    type Output = D<T>;

    fn add(self, D { reg, deriv }: &D<T>) -> Self::Output {
        D {
            reg: self.reg + reg,
            deriv: self.deriv + deriv.clone(),
        }
    }
}

But with that re-using the same D<T> twice still gets into trouble with the borrowchecker:

fn f<T>(x: &D<T>) -> D<T>
where
    T: Clone + Add<T, Output = T>,
{
    let y = x + x;
    y + x + y + x
}

On the second usage of y:

use of moved value: `y`
value used here after move

To be clear I (think I) understand why rust thinks this is a problem - the first + moves the y - and it can be solved by borrowing :

fn f<T>(x: &D<T>) -> D<T>
where
    T: Clone + Add<T, Output = T>,
{
    let y = x + x;
    &y + x + y + x
}

I've tried to make the output type of the Add instances references as well but then I run into lifetimespecifiers and "lifetime specifiers on associated types are unstable" and I don't actually think that's a good idea - I think I want whatever is calling the + to own the result, not just a reference to the result.

This all looks cumbersome, and it feels like I'm missing some pattern or approach here. Am I trying to have my cake and eat it too? Or maybe the resulting & is expected and accepted by rust programmers, and I'm worried about nothing here?

1 Answers

Since the Add trait's add Funktion takes self as its first argument, it will consume the variable. No way around that.

Given what you want to achieve, the & looks like the best solution to me. It will retain y for using it a second time as is expected.

Related