Achim
Achim

Reputation: 15702

Implementing Add trait for an enum without moving data

I'm trying to implement a recursive enum for something like a parse tree. Currently my starting point looks like this:

#[derive(Debug)]
enum Value<T: Add + Copy> {
    Sum(Rc<Value<T>>, Rc<Value<T>>),
    Scalar(T)
}

Obviously I only care about addition for now and will add other operations later. I want to implement the Add trait to enable the following:

let x = Value<f32>::Scalar(0.3);
let y = Value<f32>::Scalar(0.3);
let z = x + y;

z should now be a Value<f32>::Sum and x and y should not be moved. My question is how to implement the Add trait. Or rather for which types. My first approach looked like this:

impl<T: Add + Copy> Add for crate::Value<T> {
    type Output = Self;

    fn add(self, other: Value<T>) -> Self {
        ...
    }
}

This solution compiles, but the data is moved. If I try to change the parameters of the add method to references, it fails because the signature does not match the expected one. Makes sense, so I tried to implement the trait for &Value<T>. That seems to work, but I cannot write x + y anymore, because they are not references. I also struggled with the return type, because it cannot be &Value<T> but must be Value<T>.

I took another approach to completely work with Rc<Value<T>> but I'm not allowed to implement Add for Rc<...>. I'm aware that there is a AsRef trait, but I'm quite new to Rust and have now idea how it would fit into my use case.

Any hint how to put those pieces together and get them working would be very appreciated.

Upvotes: 2

Views: 971

Answers (1)

Netwave
Netwave

Reputation: 42708

You were almost there, you could keep your approach with Rc but wrap it in your own type:

use std::rc::Rc;
use std::ops::Add;

#[derive(Debug, Clone)]
struct WrapperValue<T: Add + Copy>(Rc<Value<T>>);

#[derive(Debug)]
enum Value<T: Add + Copy> {
    Sum(WrapperValue<T>, WrapperValue<T>),
    Scalar(T)
}

impl<T: Add + Copy> Add for WrapperValue<T> {
    type Output = WrapperValue<T>;

    fn add(self, other: WrapperValue<T>) -> Self {
        WrapperValue(Rc::new(Value::Sum(self.clone(), other.clone())))
    }
}

Playground

As you mentioned you dont want to move the value, you can also implement it for &:

impl<T: Add + Copy> Add for &WrapperValue<T> {
    type Output = WrapperValue<T>;

    fn add(self, other: &WrapperValue<T>) -> Self::Output {
        WrapperValue(Rc::new(Value::Sum(self.clone(), other.clone())))
    }
}

Playground

Upvotes: 1

Related Questions