1

I'm trying to build a language interpreter. There is a requirement to maintain a symbol table of commands as part of the machine state. Commands themselves need to be able to add new commands to the symbol table.

What I have so far is as follows:

struct Machine<'a> {
    dict: HashMap<&'a str, Box<dyn Fn(&mut Machine) -> ()>>,
}

For the interpreter function to execute a command from the symbol table, I have (simplified):

fn exec(m: &mut Machine, instr: &str) {
    let f = m.dict.get(instr).unwrap();
    f(m);
}

This gives me a compiler error:

error[E0502]: cannot borrow `*m` as mutable because it is also borrowed as immutable
 --> src/mre.rs:9:5
  |
8 |     let f = m.dict.get(instr).unwrap();
  |             ----------------- immutable borrow occurs here
9 |     f(m);
  |     -^^^
  |     |
  |     mutable borrow occurs here
  |     immutable borrow later used by call

I'm aware why this is unsafe; after all the command is allowed to remove its own entry from the symbol table. I think the correct way forward is to somehow clone f before I use it, so that the borrow of m.dict can end before the call to f.

Naively trying gives me:

fn exec(m: &mut Machine, instr: &str) {
    let f = (*m.dict.get(instr).unwrap()).clone();
    f(m);
}
error[E0599]: the method `clone` exists for struct `Box<dyn for<'r, 's> Fn(&'r mut Machine<'s>)>`, but its trait bounds were not satisfied
   --> src/mre.rs:8:43
    |
8   |       let f = (*m.dict.get(instr).unwrap()).clone();
    |               ----------------------------- ^^^^^
    |               |
    |               this is a function, perhaps you wish to call it
    |
   ::: /home/ari/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:74:1
    |
74  |   pub trait Fn<Args>: FnMut<Args> {
    |   -------------------------------
    |   |
    |   doesn't satisfy `dyn for<'r, 's> Fn(&'r mut Machine<'s>): Clone`
    |   doesn't satisfy `dyn for<'r, 's> Fn(&'r mut Machine<'s>): Sized`
    |
   ::: /home/ari/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/alloc/src/boxed.rs:194:1
    |
194 | / pub struct Box<
195 | |     T: ?Sized,
196 | |     #[unstable(feature = "allocator_api", issue = "32838")] A: Allocator = Global,
197 | | >(Unique<T>, A);
    | |_- doesn't satisfy `_: Clone`
    |
    = note: the following trait bounds were not satisfied:
            `dyn for<'r, 's> Fn(&'r mut Machine<'s>): Sized`
            which is required by `Box<dyn for<'r, 's> Fn(&'r mut Machine<'s>)>: Clone`
            `dyn for<'r, 's> Fn(&'r mut Machine<'s>): Clone`
            which is required by `Box<dyn for<'r, 's> Fn(&'r mut Machine<'s>)>: Clone`

Box<T> implements Clone when T does.Closures are Clone when certain conditions are met, so this seems like a way forward, but I'm not sure exactly how.

What's the correct way to do this?

Ari Fordsham
  • 2,437
  • 7
  • 28

1 Answers1

1

You can either:

  • Use Arc<dyn Fn(&mut Machine)> (or Rc), which is always cloneable.
  • Remove the entry from the map before using it. remove() returns an owned value, so you can invoke the function and insert it back.

You can also clone a trait object, but this is complicated.

Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77