Attempting to contain an Rc<RefCell<SomeTrait>> in an enum field with Clone

Hi!
I'm learning Rust by working my way through Crafting Interpreters and have hit a stumbling block. Note, the language in the book is called Lox which is why names below have Lox prefix.

I have a trait, LoxCallable which represents something (function, method on object, closure) which interpreter can call call() on. This is one of the fields of an enum LoxObject.

pub trait LoxCallable {
    fn arity(&self) -> i32;
    fn call(&mut self, interpreter: &mut Interpreter, arguments: &Vec<LoxObject>) -> InterpretResult<LoxObject>;
}

impl fmt::Debug for dyn LoxCallable {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "<function arity {}>", self.arity())
    }
}

impl fmt::Display for dyn LoxCallable {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "<function arity {}>", self.arity())
    }
}

impl PartialEq<dyn LoxCallable> for dyn LoxCallable {
    fn eq(&self, other: &Self) -> bool {
        todo!();
        //&self == &other
    }
}

#[derive(PartialEq, Debug, Clone)]
pub enum LoxObject {
    Boolean(bool),
    Callable(Rc<RefCell<dyn LoxCallable>>), // TROUBLE
    Nil,
    Number(f64),
    Str(String),
    Undefined,
}

I get the following error:

error[E0507]: cannot move out of `*__arg_1_0` which is behind a shared reference
  --> src/interpreter.rs:58:14
   |
58 |     Callable(Rc<RefCell<dyn LoxCallable>>),
   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ move occurs because `*__arg_1_0` has type `std::rc::Rc<std::cell::RefCell<dyn interpreter::LoxCallable>>`, which does not implement the `Copy` trait
   |
   = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)

As best I understand it, the problem here is that Rc<RefCell> doesn't have the Clone trait (thought I would have thought Rc::clone would give me that for "free").

If I get rid of the Clone from LoxObject this is fine, but I need clone elsewhere in the program.

Any thoughts on how to handle this? My thoughts are:

  1. Don't make LoxCallable a Trait, make it a struct with the requisite methods and figure out later how to make it pseudo polymorphic later as needed.

  2. Something with closures. But that's complex because these callables have a fair amount of state, eventually.

For reference, here's the original failing code in the playground.

Removing Clone produces the same error for me. Removing PartialEq results in compiling code. Implementing PartialEq manually works just fine (playground) and can serve as a temporary workaround.

I don't understand why it thinks that Rc would be moved in the implementation of PartialEq. Does anyone else have any insights? Subtle edge-case in the compiler?

1 Like

In the playground tools, a good one for derives is "expand macros", where the function looks like:

    fn eq(&self, other: &LoxObject) -> bool {
        {
            let __self_vi =
                unsafe { ::core::intrinsics::discriminant_value(&*self) };
            let __arg_1_vi =
                unsafe { ::core::intrinsics::discriminant_value(&*other) };
            if true && __self_vi == __arg_1_vi {
                match (&*self, &*other) {
                    (&LoxObject::Boolean(ref __self_0),
                     &LoxObject::Boolean(ref __arg_1_0)) =>
                    (*__self_0) == (*__arg_1_0),
                    (&LoxObject::Callable(ref __self_0),
                     &LoxObject::Callable(ref __arg_1_0)) =>
                    (*__self_0) == (*__arg_1_0),
                    (&LoxObject::Number(ref __self_0),
                     &LoxObject::Number(ref __arg_1_0)) =>
                    (*__self_0) == (*__arg_1_0),
                    (&LoxObject::Str(ref __self_0),
                     &LoxObject::Str(ref __arg_1_0)) =>
                    (*__self_0) == (*__arg_1_0),
                    _ => true,
                }
            } else { false }
        }
    }

I managed to reduce the problem down to this playground:

use std::cell::RefCell;
use std::rc::Rc;

pub trait Trait {}

impl PartialEq<dyn Trait> for dyn Trait {
    fn eq(&self, _other: &Self) -> bool {
        todo!();
    }
}

fn eq(a: &Rc<RefCell<dyn Trait>>, b: &Rc<RefCell<dyn Trait>>) -> bool {
    *a == *b
}

If you strip the Rc, just two &RefCell<dyn Trait>, it works.

If you go down to just &dyn Trait, it becomes a lifetime error:

error[E0621]: explicit lifetime required in the type of `a`
  --> src/main.rs:13:8
   |
12 | fn eq(a: &dyn Trait, b: &dyn Trait) -> bool {
   |          ---------- help: add explicit lifetime `'static` to the type of `a`: `&'static (dyn Trait + 'static)`
13 |     *a == *b
   |        ^^ lifetime `'static` required

error[E0621]: explicit lifetime required in the type of `b`
  --> src/main.rs:13:11
   |
12 | fn eq(a: &dyn Trait, b: &dyn Trait) -> bool {
   |                         ---------- help: add explicit lifetime `'static` to the type of `b`: `&'static (dyn Trait + 'static)`
13 |     *a == *b
   |           ^^ lifetime `'static` required

I'm stumped!

2 Likes

I really appreciate your looking into this. I've removed PartialEq and manually implemented per your playground. That's working, so I'm going to muddle forward. Thanks!

I filed an issue -- maybe it's a compiler bug, or maybe we're just not seeing something...

1 Like

I find it hard to believe that the first question I ask on the Rust forums may lead to a bug :slight_smile: either way, I want to thank all of you for helping me out. I was able to make the trait-based approach work and am moving forward in the Crafting Interpreters book.

1 Like

This works.

fn eq(a: &Rc<RefCell<dyn Trait>>, b: &Rc<RefCell<dyn Trait>>) -> bool {
    (*a.borrow()) == (*b.borrow())
}

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.