How can I design this application and avoid borrowing issues?


#1

I’m creating an ncurses application with 3 different views. The views can be laid out in different ways: split screen horizontally/vertically, one view takes the whole screen, etc. The problem I’m running into is that the views are all stored in a State object, and if I need to modify the view layout or affect View2 from View1, I run into borrowing issues.

The currently code looks something like this (with no mutability/borrowing problems. I’ve omitted the details of each view, as I don’t think it’s relevant):

struct State {
    views: Vec<Box<View>>,
    cur_view: usize,
}

trait View {
    fn display(&self);
    fn handle_input(&mut self) -> bool;
}

struct View1 {...}
struct View2 {...}
struct View3 {...}

impl View for View1 {...}
impl View for View2 {...}
impl View for View3 {...}

fn main() {
    let mut state = State { views: vec![Box::new(View1 {...})], cur_view: 0 };
    let mut quit = false;
    while !quit {
        display(&state);
        quit = handle_input(&mut state);
    }
}

fn display(state: &State) {
    for v in state.views.iter() {
        v.display();
    }
}

fn handle_input(state: &mut State) -> bool {
    // We only handle input for the view that is currently selected
    state.views[state.cur_view].handle_input()
}

Now let’s say View1 is the current View and we press a key in View1 and now we want to split the screen and show View2 alongside View1. I’d want the handle_input function to look something like this:

fn handle_input(state: &mut State) -> bool {
    // We only handle input for the view that is currently selected
    state.views[state.cur_view].handle_input(state)
}

That is, I need to pass the State to the View so it can update state.views with View2. However, I’m clearly borrowing state twice here:

error[E0499]: cannot borrow `*state` as mutable more than once at a time
   --> src/main.rs:217:46
    |
217 |     state.views[state.cur_view].handle_input(state)
    |     -----------                              ^^^^^- first borrow ends here
    |     |                                        |
    |     |                                        second mutable borrow occurs here
    |     first mutable borrow occurs here

I’m stuck now. How can I design this application to avoid these borrowing issues?


#2

The most direct solution is to not have the views manipulate each other. Split out any logic that needs access to multiple views into the top-level handle_inputs function, and only let views handle inputs specific to themselves. This kind of separation of concerns is usually a better design anyways.

There are other ways to work around it if that doesn’t work, but it’s probably the best place to start. If it doesn’t work, at least that will solidify the requirements for how the views need to be able to manipulate each other, which might make it clear what the right solution is.


#3

Thanks for the response. The tricky part is that when I switch to View2 for example, I may need information from View1. An example is if View1 is a list of directories, and hitting enter on a directory opens up View2 which contains the files in that directory. If I abstracted away the view switching code, then I’d need to have my handle_input function return the necessary information for the view switching code to properly create the new View, and there may be different formats of data being returned depending on the key pressed/action that needs to be executed. It may be the case that there are only a few of these cases so that this is feasible, but as I think of new features I feel like this will get fairly limiting.

If that’s the only way I’ll go with it, but I’m open to other suggestions as well.


#4

You can build your application around message passing. When you press enter in View1, you send a message to the overall handler saying “open directory X in View2”, which then sends the message “open directory X” to View2.


#5

@gsingh93 returning the data sounds like a pretty good way to do it. I’d probably encode all those possible ways to switch views as an Enum where each variant has all the necessary data to open up the new view.


#6

I don’t know enough about your design to know if there’s a reason you’re using a flat list in the first place, but it seems like in that case, there’s a relationship between the views so maybe one might be able to own the other. That would also allow more type-aware code, since you wouldn’t be relying on the top level abstraction in cases where you have a more specific context.

If they need to be siblings, then you could return a closure that makes any necessary changes to the state. That way you don’t need to worry about all the possible transitions in the top-level function.


#7

If they need to be siblings, then you could return a closure that makes any necessary changes to the state. That way you don’t need to worry about all the possible transitions in the top-level function.

Wouldn’t that lead to the same problem? You’d presumably want to return an FnMut(&mut State) -> () but that would lead to the same borrowing twice issue, wouldn’t it? Or did you have something else in mind?


#8

The closure would have to not be borrowing the view so that you can pass the state in. If the closure needs to borrow two views mutably from the state, it can store indexes and use split_at_mut to split them into separate mutable slices.

You could always use RefCell instead I suppose. The challenge is that RefCell can paper over a lot of sins, so reaching for it before evaluating other options can often create design smell. I’d try seeing if you can make the ownership structure match your usage pattern more closely first.


#9

The closure would have to not be borrowing the view so that you can pass the state in

So you mean something like this (ignore the silly State mutation that’s done in the closure, just put something in there):

struct State {
    views: Vec<Box<View>>,
    cur_view: usize,
}

trait View {
    fn display(&self);
    fn handle_input(&mut self) -> (bool, Option<Box<FnMut(&mut State) -> ()>>);
}

struct View1;
struct View2;


impl View for View1 {
    fn display(&self) {
        println!("View1");
    }

    fn handle_input(&mut self) -> (bool, Option<Box<FnMut(&mut State) -> ()>>) {
        (false, Some(Box::new(|s| s.cur_view = s.views.len() - 1)))
    }
}

impl View for View2 {
    fn display(&self) {
        println!("View2");
    }

    fn handle_input(&mut self) -> (bool, Option<Box<FnMut(&mut State) -> ()>>) {
        (true, Some(Box::new(|s| s.cur_view = s.views.len() - 1)))
    }
}

fn main() {
    let mut state = State {
        views: vec![Box::new(View1), Box::new(View2)], cur_view: 0
    };
    let mut quit = false;
    while !quit {
        display(&state);
        quit = handle_input(&mut state);
    }
}

fn display(state: &State) {
    for v in state.views.iter() {
        v.display();
    }
}

fn handle_input(state: &mut State) -> bool {
    // We only handle input for the view that is currently selected
    let (ret, mut func) = state.views[state.cur_view].handle_input();
    if let Some(ref mut func) = func {
        (func)(state);
    }
    ret
}

This compiles and runs, but I didn’t try borrowing (mutably or not) any views in the callback.


#10

Yeah, that’s it. Borrowing won’t be a problem at that point because you’re holding a unique borrow of the state. You just have to know to use split_at_mut to get multiple mutable borrows if you need it.


#11

Right, thanks. Of course the downside with this closure approach is you get a heap allocation each time.