Extracting an archive with progress bar - mutable borrow error

Viewed 116

I am trying to extract a .tar.bz file (or .tar.whatever actually) and also be able to have a xx% progress report. So far I have this:

pub fn extract_file_with_progress<P: AsRef<Path>>(&self, path: P) -> Result<()> {
    let path = path.as_ref();
    let size = fs::metadata(path)?;
    let mut f = File::open(path)?;
    let decoder = BzDecoder::new(&f);
    let mut archive = Archive::new(decoder);

    for entry in archive.entries()? {
        entry?.unpack_in(".")?;
        let pos = f.seek(SeekFrom::Current(0))?;
    }

    Ok(())
}

The idea is to use pos/size to get the percentage, but compiling the above function gets me the error cannot borrow f as mutable because it is also borrowed as immutable. I understand what the error means, but I don't really use f as mutable; I only use the seek function to get the current position.

Is there a way to work-around this, either by forcing the compiler to ignore the mutable borrow or by getting the position in some immutable way?

1 Answers

Files are a bit special. The usual read() and seek() and write() methods (defined on the Read, Seek and Write traits) take self by mutable reference:

fn read(&mut self, buf: &mut [u8]) -> Result<usize>
fn seek(&mut self, pos: SeekFrom) -> Result<u64>
fn write(&mut self, buf: &[u8]) -> Result<usize>

However, all mentioned traits are also implemented for &File, i.e. for immutable references to a file:

So you can modify a file even if you only have a read-only reference to the file. For these implementations, the Self type is &File, so accepting self by mutable reference in fact means accepting a &mut &File, a mutable reference to a reference to a file.

Your code passes &f to BzDecoder::new(), creating an immutable borrow. Later you call f.seek(SeekFrom::Current(0)), which passes f to seek by mutable reference. However, this is not allowed, since you already have an immutable borrow of the file. The solution is to use the Seek implementation on &File instead:

(&mut &f).seek(SeekFrom::Current(0))

or slightly simpler

(&f).seek(SeekFrom::Current(0))

This only creates a second immutable borrow, which is allowed by Rust's rules for references.

I created a playground example demonstrating that this works. If you replace (&f) with f you get the error you originally got.

Related