3

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?

Boiethios
  • 38,438
  • 19
  • 134
  • 183
thanasis2028
  • 125
  • 5
  • `seek()` takes `&mut self`, so this is where the mutable borrow happens. Looking at the crate docs, couldn't you use `decoder.total_in()` instead? – Tarmil Oct 26 '18 at 09:08
  • Please review how to create a [MCVE] and then [edit] your question to include it. We cannot tell what crates, types, traits, fields, etc. are present in the code. Sepcifically, we cannot tell where `BzDecoder` and `Archive` come from, plus some other traits. Try to produce something that reproduces your error on the [Rust Playground](https://play.rust-lang.org) or you can reproduce it in a brand new Cargo project. There are [Rust-specific MCVE tips](//stackoverflow.com/tags/rust/info) as well. – Shepmaster Oct 26 '18 at 15:15

1 Answers1

8

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.

Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
  • Nice one thanks. I didn't know about the &File traits implementation. But how does the `(&f).seek()` version work? Shouldn't the pointer be mutable according to the trait? – thanasis2028 Oct 26 '18 at 15:12
  • See also: [Why is it possible to implement Read on an immutable reference to File?](https://stackoverflow.com/q/31503488/155423). – Shepmaster Oct 26 '18 at 15:14
  • 1
    @thanasis2028 `(&f).seek()` works for exactly the same reason that `f.seek()` works. Methods are first looked up on the type of the receiver itselt, say `T`, then on `&T`, then on `&mut T`, then on objects obtained by iteratively dereferencing the receiver. See [this answer](https://stackoverflow.com/a/52534876/279627) for further details. – Sven Marnach Oct 26 '18 at 15:23