Borrowing problem

I’m trying to build a small TCL interpreter and I’m stuck with a borrowing problem:

error[E0502]: cannot borrow `*self` as mutable because `self.commands` is also borrowed as immutable                                                                                                                                   
  --> src/lib.rs:97:13                                                                                                                                                                                                                 
   |                                                                                                                                                                                                                                   
96 |         let cmd = &self.commands.get(cmd_name).unwrap();                                                                                                                                                                          
   |                    ------------- immutable borrow occurs here                                                                                                                                                                     
97 |         cmd(self, params)                                                                                                                                                                                                         
   |             ^^^^ mutable borrow occurs here                                                                                                                                                                                       
98 |     }                                                                                                                                                                                                                             
   |     - immutable borrow ends here                 

Here, self is the interpreter context which, for example, stores all the available commands in a hash map. The first line retrieves the command we want to execute from that hash map and the second line executes it. I want to pass the context into the command so it could, for example, add more commands. This inherently creates some aliasing, since the command has a self reference and the context which also references that. I’m not sure how to resolve that conflict.

  1. Make mutable borrow immutable? Then I cannot add more commands.
  2. Create a copy/clone? I don’t know how. It seems to be tricky because a closure is involved?
  3. The unsafe cop out? It might be reasonable, since in contrast to the compiler, I know that the closure object is actually immutable even if the whole context is not. Maybe there is a safer way to tell the compiler that?

You may pass &self rather than &mut self to the command, and then if you need to mutate something via &self, use interior mutability.

Another way is to wrap the commands in Rc, so that get returns a clone, so &self.commands isn’t borrowed.

1 Like

Another option would be to have the command return a result indicating what it wants done, such as adding a new command.

I went with the RC way although that means some slight runtime overhead to satisfy the borrow checker. Interior mutability would be the optimization if it ever becomes necessary. This is just a simple interpreter after all. The result solution seems too complicated to me, since that changes a lot of places.

Now puts "Hello World" works. :tada:

Depending on the content of your commands (i.e. their state), you may be able to just clone them outright, without going through an Rc.

Does not look like it. When I change the Rc to Box like that

-        ri.register_command("puts", Rc::from(move |_r:&mut RustclInterpreter, params:Vec<Token>| {
+        ri.register_command("puts", Box::from(move |_r:&mut RustclInterpreter, params:Vec<Token>| {

-pub type FnCmd<'a> = Rc<Fn(&mut RustclInterpreter, Vec<Token>)->&'a str>;
+pub type FnCmd<'a> = Box<Fn(&mut RustclInterpreter, Vec<Token>)->&'a str>;

Then the error returns:

error[E0502]: cannot borrow `*self` as mutable because `self.commands` is also borrowed as immutable                                                                                                                                   
   --> src/lib.rs:107:13                                                                                                                                                                                                               
    |                                                                                                                                                                                                                                  
106 |         let cmd = &self.commands.get(cmd_name).unwrap().clone();                                                                                                                                                                 
    |                    ------------- immutable borrow occurs here                                                                                                                                                                    
107 |         cmd(self, params)                                                                                                                                                                                                        
    |             ^^^^ mutable borrow occurs here                                                                                                                                                                                      
108 |     }                                                                                                                                                                                                                            
    |     - immutable borrow ends here           

Note the .clone(). It works for Rc but now seems to be ignored? Shouldn't there be an error if a Box cannot be cloned?

Ah, you’re using type erased closures. Yeah, it won’t work well then.

Any reason you decided to use trait objects instead of an enum to represent commands? Is the set of command impls unknown to you (ie caller can register arbitrary ones)?

Yes, the register_command is public interface. Also, scripts should be able to define new commands.