Matching against multiple Boxed values

Viewed 51

I have the following code which compiles fine

#[derive(Debug, PartialEq, Clone)]
pub enum Expression {
    Const(i32),
    Neg(Box<Expression>),
    Add(Box<Expression>, Box<Expression>),
}

fn simplify(expr: &Expression) -> Expression {
    match expr {
        Expression::Neg(x) => match **x {
            Expression::Const(n) => Expression::Const(-n),
            _ => expr.clone() 
        },  

        // GIVES ERROR
        // Expression::Add(x, y) => match (**x, **y) {
        //     (Expression::Const(n), Expression::Const(m)) => Expression::Const(n + m),
        //     _ => expr.clone() 
        // },
    
    
        Expression::Add(x, y) => match **x {
            Expression::Const(n) => match **y {
                Expression::Const(m) => Expression::Const(n + m), 
                _ => expr.clone() 
            }   
            _ => expr.clone() 
        }   


        _ => expr.clone() 
    }   
}

But if I replace the Expression::Add arm with the commented out version, I get the following compiler error

error[E0507]: cannot move out of `**x` which is behind a shared reference
  --> src/lib.rs:21:41
   |
21 |         Expression::Add(x, y) => match (**x, **y) {
   |                                         ^^^ move occurs because `**x` has type `Expression`, which does not implement the `Copy` trait

error[E0507]: cannot move out of `**y` which is behind a shared reference
  --> src/lib.rs:21:46
   |
21 |         Expression::Add(x, y) => match (**x, **y) {
   |                                              ^^^ move occurs because `**y` has type `Expression`, which does not implement the `Copy` trait

For more information about this error, try `rustc --explain E0507`.

Is there a reason we may match against the lone **x but not in a tuple like (**x, **y)? Is the former actually being converted or hiding some syntactic sugar? Is there a simpler way of writing this Add arm than with the two nested matches?

EDIT: I also see that there is a ref keyword, which is supposed to address something like this, but changing my tuple match expression to (ref **x, ref **y) gives a syntax error (error: expected expression, found keyword ref).

2 Answers

TL;DR: Match against (&**x, &**y).


What's going on here is interesting. And the TL;DR is: when you match v {}, you don't read v. You create a place for v.

A place is something we can read from. Or write to. Or do nothing with. The important point is that creating place alone does not involve such operation. You can read/write it later, but when you create it, it is just a place.

In your match, the type of x and y is &Box<Expression>. When we match **x, we don't read x. Thus, we don't move **x either. What we do is create a place for **x. We then match this place against Expression::Const(n). Now we read x and extract n out of it. But n is i32 - Copy - so this is fine.

In contrast, when you use a tuple (**x, **y), since you don't match against **x and **y directly you do read them. And because you read them, and they are not Copy (Expression), you move out of them. Now this is an error because you cannot move out of shared references. You match them after, but they're already moved.

You can try match(x.as_ref(),y.as_ref())

Related