Captured variable cannot escape `FnMut` closure body


#[cfg(test)]
mod tests {
    use std::fs::File;
    use std::path::Path;

    use wax::{Glob, Pattern};
    use zip::read::ZipFile;
    use zip::ZipArchive;

    #[test]
    fn it_works() {
        let dex = Glob::new("*.dex").unwrap();
        let path = Path::new("/Users/leojangh/Downloads/sample.apk");
        let mut archive = ZipArchive::new(File::open(path).unwrap()).unwrap();
        let dex_files: Vec<_> = archive.file_names()
            .filter(|&name| dex.is_match(name))
            .map(|x| archive.by_name(x).unwrap())
            .collect();
    }
}

This code cannot be compiled:

error[E0502]: cannot borrow `archive` as mutable because it is also borrowed as immutable
  --> src/lib.rs:21:18
   |
19 |         let dex_files: Vec<_> = archive.file_names()
   |                                 -------------------- immutable borrow occurs here
20 |             .filter(|&name| dex.is_match(name))
21 |             .map(|x| archive.by_name(x).unwrap())
   |              --- ^^^ ------- second borrow occurs due to use of `archive` in closure
   |              |   |
   |              |   mutable borrow occurs here
   |              immutable borrow later used by call

and

error: captured variable cannot escape `FnMut` closure body
  --> src/lib.rs:21:22
   |
18 |         let mut archive = ZipArchive::new(File::open(path).unwrap()).unwrap();
   |             ----------- variable defined here
...
21 |             .map(|x| archive.by_name(x).unwrap())
   |                    - -------^^^^^^^^^^^^^^^^^^^^
   |                    | |
   |                    | returns a reference to a captured variable which escapes the closure body
   |                    | variable captured here
   |                    inferred to be a `FnMut` closure
   |
   = note: `FnMut` closures only have access to their captured variables while they are executing...
   = note: ...therefore, they cannot allow references to captured variables to escape
   = note: requirement occurs because of the type `ZipFile<'_>`, which makes the generic argument `'_` invariant
   = note: the struct `ZipFile<'a>` is invariant over the parameter `'a`
   = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance

How could I reach my goal? It looks like the function passed into map will be invoked many times,but the method by_name has a &mut self reference? I can't realize the second error message.

related but not quite precise describes the error. because the return type ZipFile of ZipArchive::by_name() (mutably, a.k.a. exclusively) borrows the archive, you cannot have more than one ZipFile at a time. which makes sense if you think about it. the zip archive cannot randomly seek between different files, you find a file, process it, then you move to the next file.

but you need to collect the file names first, because during the iteration, all the names are borrowed from archive too.

for example, if all you want is the contents of the files, you do it like this:

let dex_file_names: Vec<_> = archive
	.file_names()
	.filter(|name| !name.is_empty())
	.map(String::from)
	.collect();
let contents: Vec<_> = dex_file_names
	.iter()
	.map(|name| {
		let f = archive.by_name(name).unwrap();
		f.extra_data().to_owned()
	})
	.collect();

after that, the contents variable is a vector of vector of bytes, i.e. Vec<Vec<u8>>, containing the data extracted from the files.

1 Like

Thank you very much for your explain.But I still have a question how do you known there can only be at most one of ZipFile at a time and sequential access? Cause I have never seen some docs about this, so it is infered to reference &mut self ? It does make sense bu I didn't notice this implementation detail.

The fact that the function signature has &'a mut self and ZipFile<'a> tells you that.

The types of references a function takes are not an implementation detail. They are a part of the interface and they tell you what you can and can't do with that function. Noticing them is an important skill.

3 Likes

One potential point of confusion is that while mut or non-mut bindings are primarily a lint in your own code and an implementation detail in function headers...

fn foo1(mut x: String) { /* ... */ }
fn foo2(x: String) {
    let mut x = x;
    // ...
}

...&mut T and &T are two different types with different implications and capabilities. Despite the syntax, a &mut T is an exclusive borrow and a &T is a shared borrow. Hence why the signature tells you there can only be at most one at a time.

When you refer to RustDoc generated documentation, it doesn't even show mut bindings (because they are an implementation detail). But &mut vs & is a very important part of the API.

1 Like