Lifetimes problem with split_once method of vectors

47 Views Asked by At

I am trying to implement a file lines reader that would not do so many checks and allocations to speed up some file reading.

This should be valid, but the compiler is complaining about borrowing self.buffer as mutable while it is still borrowed as immutable at the beginning of the read_line method. The problem seems to be related to the lifetimes, but I can't explain to the compiler that the first borrow is dropped and has nothing to do with the None => arm anymore.

struct FastFileLinesReader<B: BufRead> {
    stream: B,
    buffer: Vec<u8>,
    cursor: usize,
}

impl<B: BufRead> FastFileLinesReader<B> {
    fn new(stream: B) -> Self {
        Self {
            stream,
            buffer: vec![1024],
            cursor: 0,
        }
    }

    fn read_line(&mut self) -> Option<&[u8]> {
        let line = {
            let remainder = &self.buffer[self.cursor..];
            remainder.split_once(|v| *v == b'\n' as u8)
        };
        match line {
            Some((line, _)) => {
                self.cursor += line.len() + 1;
                return Some(line);
            }
            None => {
                self.buffer.copy_within(self.cursor.., 0);
                self.cursor = 0;
                let n = self.stream.read(&mut self.buffer[self.cursor..]).ok()?;
                if n == 0 {
                    return None;
                }
                return self.read_line();
            }
        };
    }
}

The error:

error[E0502]: cannot borrow `self.buffer` as mutable because it is also borrowed as immutable
   --> src/bin/main.rs:110:17
    |
99  |     fn read_line(&mut self) -> Option<&[u8]> {
    |                  - let's call the lifetime of this reference `'1`
100 |         let line = {
101 |             let remainder = &self.buffer[self.cursor..];
    |                              ----------- immutable borrow occurs here
...
107 |                 return Some(line);
    |                        ---------- returning this value requires that `self.buffer` is borrowed for `'1`
...
110 |                 self.buffer.copy_within(self.cursor.., 0);
1

There are 1 best solutions below

0
kmdreko On BEST ANSWER

@isaactfa was right that this case is a known limitation of the borrow checker that incorrectly thinks an early-return overlaps with the rest of the function.

In this case though, you can side-step the problem by avoiding references you get from .split_once and just use indexes instead:

fn read_line(&mut self) -> Option<&[u8]> {
    let newline = self.buffer[self.cursor..].iter().position(|v| *v == b'\n' as u8);
    
    if let Some(idx) = newline {
        self.cursor += idx + 1;
        return Some(&self.buffer[self.cursor..][..idx]);
    } else {
        self.buffer.copy_within(self.cursor.., 0);
        self.cursor = 0;
        let n = self.stream.read(&mut self.buffer[self.cursor..]).ok()?;
        if n == 0 {
            return None;
        }
        return self.read_line();
    }
}