[Solved] Avoiding "can't borrow as mutable as also borrowed as immutable"

I'm trying to expose functions associated with a struct so that I can call them from the terminal while the program is running.

I've had some success by defining a trait for this which I implement the on the structs I'm interested in. Although I don't fully grok lifetime annotations, I was able to follow the compiler error messages to add them where appropriate (or at least, to make it work). However, I'm now running into problems with the borrow checker. I understand what the error message is saying, but can't figure out how one would go about restucturing the program to avoid it.

Here's a trimmed down version of the code demonstrating how I'm trying to use it and therefore the problem I'm having:

#[macro_use]
extern crate failure;

use failure::Error;

use std::io;
use std::sync::mpsc;
use std::thread;

use std::collections::HashMap;

pub trait CanCommand {
    fn module_commands(&self, commands: String) -> Result<Option<String>, Error>;
}

pub struct CommandInterpreter<'a> {
    modules: HashMap<String, &'a dyn CanCommand>,
}

impl<'a> CommandInterpreter<'a> {
    pub fn new() -> CommandInterpreter<'a> {
        CommandInterpreter {
            modules: HashMap::new(),
        }
    }

    pub fn add_module(&mut self, module: String, function_name: &'a CanCommand) {
        self.modules.insert(module, function_name);
    }

    pub fn list_modules(&self) -> Option<Vec<String>> {
        match self.modules.is_empty() {
            true => None,
            false => {
                let mut module_list = Vec::new();
                for key in self.modules.keys() {
                    module_list.push(key.to_string());
                }
                Some(module_list)
            }
        }
    }

    pub fn run_command(&self, command: String) -> Result<Option<String>, Error> {
        //Splits in two at the first space in the line.
        //The first segment needs to match a stored module name.
        //The second segment is passed into the associated function.
        let mut command = command.splitn(2, ' ');
        let module = command.next().unwrap();
        match module.is_empty() {
            false => {
                let module_command = command
                    .next()
                    .ok_or_else(|| format_err!("No module command"))?;

                match self.modules.get(module) {
                    Some(&command_function) => {
                        command_function.module_commands(module_command.to_string())
                    }
                    None => Err(format_err!("This is not a module",)),
                }
            }
            true => Err(format_err!("No module")),
        }
    }
}

struct Window {
    title: String,
}

impl Window {
    pub fn poll_events(&mut self) -> bool {
        true
    }

    pub fn swap_buffers(&self) -> Result<(), Error> {
        Ok(())
    }

    pub fn get_title(&self) -> String {
        self.title.to_owned()
    }
}

impl CanCommand for Window {
    fn module_commands(&self, commands: String) -> Result<Option<String>, Error> {
        //Splits in two at the first colon in the line.
        //The first segment, with whitespace trimmed, needs to match a command in this match block.
        //The second segment, if it exists, is used for passing in arguments to the called functions.
        let mut split_args = commands.splitn(2, ':');
        match split_args.next().unwrap().trim() {
            "get title" => Ok(Some(
                "The window title is ".to_owned() + &self.get_title().to_string(),
            )),
            _ => Err(format_err!("This is not a valid command.")),
        }
    }
}

fn main() {
    let mut window = Window {
        title: "Title".to_string(),
    };
    println!("Window title is: {}", window.get_title());
    let mut command_interpreter = CommandInterpreter::new();
    command_interpreter.add_module("window".to_string(), &window);

    println!(
        "Installed modules: {:?}",
        command_interpreter.list_modules().unwrap_or_default()
    );
    let (command_input, command_received) = mpsc::channel();
    let _input_thread = thread::spawn(move || loop {
        let mut input = String::new();
        io::stdin().read_line(&mut input).unwrap();
        command_input.send(input.trim().to_string()).unwrap();
    });

    let mut running = true;

    while running {
        running = window.poll_events();

        window.swap_buffers().unwrap();

        if let Ok(command) = command_received.try_recv() {
            match command_interpreter.run_command(command.to_string()) {
                Ok(Some(response)) => println!("{}", response),
                Err(message) => println!("{}", message),
                _ => (),
            };
        }
    }
}

gives

error[E0502]: cannot borrow `window` as mutable because it is also borrowed as immutable
   --> src\main.rs:123:19
    |
107 |     command_interpreter.add_module("window".to_string(), &window);
    |                                                          ------- immutable borrow occurs here
...
123 |         running = window.poll_events();
    |                   ^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
...
128 |             match command_interpreter.run_command(command.to_string()) {
    |                   ------------------- borrow used here in later iteration of loop

Any pointers would be greatly appreciated!

I mean, fundamentally, you have to remove the multiple paths to Window. There's so many ways you could conceivably do that; a big part of writing Rust programs is working those sorts of architectural problems through. Off the top of my head:

  • Ask command_interpreter for temporary access to Window when you need it.
  • Have command_interpreter ask for temporary access to Window when it needs it.
  • Don't give command_interpreter access to Window at all. Have it tell your loop which object you need to pass the command on to.
  • Split the different parts of Window out, assuming that's even possible.
  • Put Window behind some kind of cell or lock so you both have to ask that for temporary access.
1 Like

Thanks for the help!

Following your hints, I have now learnt about RefCell, and have successfully managed to get the code to compile and work.

I've put window inside a RefCell, implemented the CanCommand trait on RefCell<Window>, and can access the desired methods with window.borrow() and window.borrow_mut. It doesn't look particularly nice, but it does what I want it to, which is a good place to start from!

Before you move on with RefCell, consider alternatives :slight_smile:; I'm of the opinion that you really want to try hard avoiding it because it introduces runtime panics if you slip up in the borrow protocol.

Here is a playground example (I had to replace failure with plain old String as error types since that crate isn't available in the playground) that passes &mut Window as a contextual parameter to CanCommand - I'm not even sure if you need a mutable borrow there, but in case something wants to change the title, that would help. Then, we add a WindowTitle struct to the command list, which simply returns the window's title.

Not sure if that's workable in the real case, but worth a look. It looks like Window is not really a command, per say, but is rather a component that participates in driving the loop - you can see that sort of show through because you're explicitly needing to work with Window in the loop, and can't treat it as only a generic CanComponent that you don't know about.

Using message passing, as @DanielKeep mentioned (as well as his other suggestions), might be another approach to consider.

But, hold off on RefCell until you've exhausted other options :slight_smile:.

I do indeed agree, from what I can tell from the documentation. But I'm quite happy to have got it operational for the moment. I can then work on alternate features as a change from compiler error after compiler error!

Essentially, the Window is a wrapper for a glutin window and event loop. I naïvely stuck them both together in one struct to keep all the glutin related stuff in one structure, but it's the event loop part that was causing the borrow errors (i/e/ the poll_events). Splitting them up should enable me to access the window happily, as the loop would be separate, and I now suspect that this is the direction I will refactor in.

It's rather interesting training one's brain to think about things the "right" way. For example, at one point I was trying to implement the CanCommand trait on the CommandInterpreter, as there were some functions that in my mind were most logically connected to it. Unsurprisingly, I ran into errors trying to pass references from the command interpreter to itself, and it took me a surprisingly long time to realise that the most sensible thing to do would be to just create a separate empty struct and implement the trait and functions on that. The more one gets used to using the correct patterns, the easier it will be to train one's brain into using them in the first place!

1 Like

And success!

I've eliminated the RefCells, by managing to take out the fn poll_events(&mut self) and put that in an EventsLoop struct, thereby allowing me to implement CanCommand on Window again, and pass &window happily to command_interpreter and the rewritten events_loop.poll_events(&window).

Now I've got this working, hopefully I'll be able to continue to use the same pattern for any future struct that I want to expose functions for.

Thanks everyone!