Pass borrowed struct and struct field to function

I have a struct representing my terminal, with terminal size h and w and a io::Stdout struct used to print to the terminal in RAW mode:

pub struct RcdTerm {
    h: u16,
    w: u16,
    stdout: io::Stdout,

I also have a function to print an highlighted line at line line:

pub fn line_highlight(term: &mut RcdTerm, line: u16) {
    ...
}

Now, at some point I want to highlight the latest line with something like this:

...
let mut term = terminal_setup();
line_highlight(&mut term, term.h);

This is failing with error[E0503]: cannot use term.h because it was mutably borrowed. If this makes sense from the point of view of the borrow checker I fail to understand why this is a problem for at least two reasons:

  1. I can still do something like the following but it is ugly:
let foo = term.h;
line_highlight(&mut term, foo);
  1. Since my function is accepting u16, shouldn't the value of term.h just be copied into the local line variable (parameter) when the function is called? No pointer / heap is involved here, so why is this a problem?

This is because it gets desugared to

let term_mut = &mut term;
let h = term.h; // oops &mut must have exclusive access
line_highlight(term_mut, h);

Yes, I get that. That's why I said that the error makes sense from the borrow checker point of view but I was probably expecting the compiler to be a bit smarter than that since the h value is being copied into the line variable at function-call time. But I see that since it is desugaring before the function call this is causing the problem.

There is one case where the compiler is smarter: If the function is a method (has a self parameter), then two-phase borrows allow later arguments to access the struct without invalidating the borrow of the self argument. For example, this works:


impl RcdTerm {
    pub fn line_highlight(&mut self, line: u16) {
        // ...
    }
}

fn main() {
    let mut term = terminal_setup();
    term.line_highlight(term.h);
}

Playground

You can also pass them in the other order:

pub fn line_highlight(line: u16, term: &mut RcdTerm) {
    // ...
}

fn main() {
    let mut term = terminal_setup();
    line_highlight(term.h, &mut term);
}

playground

Two-phase borrows also work when re-borrowing from an existing mutable borrow. For example, this compiles:

pub fn line_highlight(term: &mut RcdTerm, line: u16) {
    // ...
}

fn main() {
    let mut term = terminal_setup();
    let term = &mut term;
    line_highlight(term, term.h);
}
2 Likes

Yeah, this is exactly what I ended up doing at the end. I had no idea that this was called two-phase borrows, I learned a new thing, thank you!

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.