0

Thanks to @jmb for pointing out that this is a known compiler limitation that has to do with lexical lifetimes. There is likely no easy way to solve the issue currently.

I ended up working around it by refactoring my code not to use the returned reference from self._read_to_buffer(), but to access the internal buffer directly.

if &self.buffer != "err" {
    Ok(&self.buffer)
} else {
    // The next line contains the explanation of the error, so read it
    let e = self._read_to_buffer();
    Err(format!("Unable to read line because of error: {e}"))
}

This was not a particularly nice solution for my actual problem as it involved exposing private variables through multiple layers of abstraction. A better solution may be possible though e.g. [https://crates.io/crates/polonius-the-crab], but I have not yet been able to get one to work. I am also not sure that I'm comfortable with bringing that crate into production.


Consider an input that contains either valid lines, or the message "err" followed by and explanation of the error, e.g:

"foo
err
explanation
bar
baz"

You want to return the line if it was valid, or an error with the included explanation otherwise.

use std::io::{BufRead, Cursor};

fn main() {
    let mut s = ValidLineReader {
        input: Cursor::new("foo\nerr\nexplanation\nbar\nbaz"),
        buffer: String::new(),
    };

    dbg!(s.next_line());
    dbg!(s.next_line());
    dbg!(s.next_line());
    dbg!(s.next_line());
}

struct ValidLineReader {
    input: Cursor<&'static str>,
    buffer: String,
}

impl ValidLineReader {
    pub fn next_line(&mut self) -> Result<&str, String> {
        let next = self._read_to_buffer();
        if next != "err" {
            Ok(next)
        } else {
            // The next line contains the explanation of the error, so read it
            let e = self._read_to_buffer();
            Err(format!("Unable to read line because of error: {e}"))
        }
    }

    fn _read_to_buffer(&mut self) -> &str {
        self.input.read_line(&mut self.buffer).unwrap();
        &self.buffer
    }
}

This will fail with the following error message:

  --> src/main.rs:27:21
   |
21 |     pub fn next_line(&mut self) -> Result<&str, String> {
   |                      - let's call the lifetime of this reference `'1`
22 |         let next = self._read_to_buffer();
   |                    ---------------------- first mutable borrow occurs here
23 |         if next != "err" {
24 |             Ok(next)
   |             -------- returning this value requires that `*self` is borrowed for `'1`
...
27 |             let e = self._read_to_buffer();
   |                     ^^^^^^^^^^^^^^^^^^^^^^ second mutable borrow occurs here

I understand that doing something like

let e = self._read_to_buffer();
println!("Unable to read line because of error: {e}");
Ok(next)

would be a reference error, since a call to self._read_to_buffer() mutates the buffer that next is pointing to. But we do not use next in the else branch, so why does the compiler seem to think that it is still borrowed?

  • 1
    Does this answer your question: [Why does returning early not finish outstanding borrows?](https://stackoverflow.com/a/51350561/5397009) – Jmb Jan 13 '23 at 13:10
  • TLDR: this is a known limitation of the current borrow-checker implementation, that should be fixed by the experimental [Polonius](https://rust-lang.github.io/polonius/) borrow-checker available on nightly with the `-Zpolonius` flag – Jmb Jan 13 '23 at 13:13
  • @Jmb You're probably right, it's a consequence of lexical lifetimes. I still need to find a workaround though, I don't suppose you have any ideas? – Sebastian Holmin Jan 13 '23 at 14:09
  • You can try with the [`polonius_the_crab` crate](https://crates.io/crates/polonius-the-crab). – Jmb Jan 13 '23 at 14:24

0 Answers0