Refecell containing enums can't be pattern matched

I can't seem to get this code to allow me to use refcell for interior mutability.

I'm using refcells, and I use the borrow_mut() function like so:

struct AllMonkeys<'a>(HashMap<&'a str, RefCell<Monkey<'a>>>);
impl<'a> AllMonkeys<'a> {
    fn calculate(&self, name: &str) -> i64 {
        match self.0[name].borrow_mut() {
            Monkey::Val(num) => num,
            mut monkey @ Monkey::Job {
                left,
                right,
                operand,
            } => {
                let left_val = self.calculate(left);
                let right_val = self.calculate(right);
                let val = match operand {
                    Operand::Plus => left_val + right_val,
                    Operand::Minus => left_val - right_val,
                    Operand::Mult => left_val * right_val,
                    Operand::Div => left_val / right_val,
                };
                *monkey = Monkey::Val(val);
                val
            }
        }
    }
}

this complains of a type missmatch:

error[E0308]: mismatched types
  --> src/day21.rs:12:13
   |
11 |         match self.0[name].borrow_mut() {
   |               ------------------------- this expression has type `RefMut<'_, day21::Monkey<'a>>`
12 |             Monkey::Val(num) => num,
   |             ^^^^^^^^^^^^^^^^ expected `RefMut<'_, Monkey<'_>>`, found `Monkey<'_>`
   |
   = note: expected struct `RefMut<'_, day21::Monkey<'a>, >`
                found enum `day21::Monkey<'_>`

fair enough. so, I'll try dereferencing the refmut to make the type checker happy:

    fn calculate(&self, name: &str) -> i64 {
        match *self.0[name].borrow_mut() {
            Monkey::Val(num) => num,
            mut monkey @ Monkey::Job {
                left,
                right,
                operand,
            } => {
                let left_val = self.calculate(left);
                let right_val = self.calculate(right);
                let val = match operand {
                    Operand::Plus => left_val + right_val,
                    Operand::Minus => left_val - right_val,
                    Operand::Mult => left_val * right_val,
                    Operand::Div => left_val / right_val,
                };
                monkey = Monkey::Val(val);
                val
            }
        }
    }

but now the type checker complains that I'm moving out of a reference:

error[E0507]: cannot move out of dereference of `RefMut<'_, day21::Monkey<'_>>`
  --> src/day21.rs:11:15
   |
11 |         match *self.0[name].borrow_mut() {
   |               ^^^^^^^^^^^^^^^^^^^^^^^^^^
12 |             Monkey::Val(num) => num,
13 |             monkey @ Monkey::Job {
   |             ------ data moved here
...
16 |                 operand,
   |                 ------- ...and here
   |
   = note: move occurs because these variables have types that don't implement the `Copy` trait
help: consider removing the dereference here

I'm stumped, what do I do?

full code(I'm using include_str!() so this won't compile unless you add a file at the expected location): Playground

Either do &* to create a reference to the dereferenced location, or import the Deref trait so you can call the deref method directly (which returns a reference)

match &*self.0[name].borrow_mut() doesn't work because I need a mutable reference to change the monkey later. If I take a mutable reference of the dereference(&mut *), the compiler still tells me that I'm moving the value:

error[E0382]: borrow of moved value
  --> src/day21.rs:16:17
   |
11 |         match &mut *self.0[name].borrow_mut() {
   |               ------------------------------- move occurs because value has type `&mut day21::Monkey<'_>`, which does not implement the `Copy` trait
12 |             Monkey::Val(num) => *num,
13 |             monkey @ Monkey::Job {
   |             ------ value moved here
...
16 |                 operand,
   |                 ^^^^^^^ value borrowed here after move

If it were me, I would probably write it like this:

impl<'a> AllMonkeys<'a> {
    fn calculate(&self, name: &str) -> i64 {
        let mut monkey = self.0[name].borrow_mut();

        match *monkey {
            Monkey::Val(num) => num,
            Monkey::Job {
                left,
                right,
                ref operand,
            } => {
                let left_val = self.calculate(left);
                let right_val = self.calculate(right);
                let val = match operand {
                    Operand::Plus => left_val + right_val,
                    Operand::Minus => left_val - right_val,
                    Operand::Mult => left_val * right_val,
                    Operand::Div => left_val / right_val,
                };
                *monkey = Monkey::Val(val);
                val
            }
        }
    }
}

(playground)

1 Like

thanks, this solution is great! I'd like to call out when I was writing this for myself, I glossed over the ref keyword before operand. I gather this makes it so that the match statement only takes a ref, rather than moving the value? I haven't seen that before.

Here's some documentation for that keyword. I didn't know this existed before today!

Yep. You can throw a ref in front of pretty much anything when pattern matching and it'll take a reference to that thing instead of moving it. The left and right are &str, which is Copy, so I didn't have to worry that we were moving them out of the *monkey.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.