Cannot borrow `buf_reader` as mutable more than once at a time

Hello community,
I'm new to Rust and I'm learning to use the BufReader struct. I don't understand why this code doesn't compile:

use std::fs;
use std::io;
use std::io::BufRead;

pub fn read_a_file() {
    let file_name = "/tmp/text_file.txt";
    let input_file = fs::File::open(file_name).expect("failed to open file");
    let mut buf_reader = io::BufReader::new(input_file);
    loop {
        let content = buf_reader.fill_buf().expect("failed to fill buffer");
        if content.is_empty() {
            println!("no more data to read");
            break;
        }
        for u8_value in content {
            print!("{}", *u8_value as char);
        }
        buf_reader.consume(content.len());
    }
    println!();
}

Rustc says: error[E0499]: cannot borrow buf_reader as mutable more than once at a time
--> src/check_read.rs:18:9
|
10 | let content = buf_reader.fill_buf().expect("failed to fill buffer");
| --------------------- first mutable borrow occurs here
...
18 | buf_reader.consume(content.len());
| ^^^^^^^^^^^^^^^^^^^-------------^
| | |
| | first borrow later used here
| second mutable borrow occurs here

It seems that everyone on the internet first calls fill_buf() then consume(), I don't understand why it doesn't work in my code. Can someone please help explain? Thanks in advance.

        let len = content.len();
        buf_reader.consume(len);

Do this, as in the example for fill_buf. The content has borrowed the buffer, so getting its length before doing another borrow in consume is needed.

1 Like

it's an unfortunate and confusing limitation of the current borrow checker. search "two phase borrows" if you are interested in the details. in short, content borrows buf_reader, and its last use is buf_reader.consume(content.len()), so the lifetime of the borrow cannot be shorten, but BufReader::consume need self to also be exclusively (aka mut) borrowed, so it's a conflict. the workaround is:

-       buf_reader.consume(content.len());
+       let len = content.len();
+       buf_reader.consume(len);

now the borrowed lifetime of content can be shorten because the last use is the statement let len = content.len(); which is before the statement buf_reader.consume(len);

1 Like

I should add, I say it's a limitation, because it's not a bug, but indented behavior. however it could be confusing for new learners when shared (aka immutable) borrows and exclusive borrows behave differently due to the way two phase borrows are handled by the borrow checker.

@nerditation I see the problem here but I don't quite understand how it relates to two phase borrows. Could you explain please?

due to semantics that the receiver of a method is always evaluated before other arguments, in particular, when the receiver is &mut self, without two phase borrows, all other arguments (the expression itself, not what it evaluates to) cannot have references to the receiver value, which is quite limiting. two phase relaxes this limitation a little bit to allow shared references. the canonical example is:

let mut v = vec![];
v.push(v.len());
v.push(v[0]);

the way it's implemented is, when the receiver is evaluated, it is temporarily "downgraded" to a reserved borrow, which the borrow checker treated as a shared borrow, and then gets "activated" to exclusive borrow when the function body is executed.

but to a newcomer who just learned mut reference, it seems inconsistent, e.g.

struct Foo(isize);
impl Foo {
    fn get(&self) -> isize { self.0 }
    fn add(&mut self, x: isize) -> isize {
        self.0 += x;
        self.0
    }
}

fn test1() {
    let mut foo = Foo;
    foo.add(foo.get()); // <- this is ok, two phase borrow
    foo.add(foo.add(1)); // <- error, two phase borrow can't help
}

fn test2() {
    let mut foo = Foo;
    let foo_ref = &mut foo;
    foo_ref.add(foo_ref.get()); // <- this is ok, two phase borrow
    foo_ref.add(foo.get()); // <- error, looks similar but no two phase borrow
}

things get even messier when Deref and DerefMut get involved:

struct Bar(Foo);
impl Deref for Bar {
    type Target = Foo;
    fn deref(&self) -> &Foo { &self.0 }
}
impl DerefMut for Bar {
    fn deref_mut(&mut self) -> &mut Foo { &mut self.0 }
}

fn test3() {
    let mut foo = Foo(42);
    foo.add(foo.get()); // ok
    let mut bar = Bar(foo);
    bar.add(bar.get()); // error
}

bottom line is, understanding the borrow checker is hard for rust learners, it's even harder to grasp the compiler message if they stumbled upon two phase borrows "by accident". but once you wrap your head around, you don't even see the problem anymore.

2 Likes

Ok I understand now what you meant. :slight_smile: Thanks! Regarding the "look and feel" this might seem inconsistent for a beginner indeed.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.