Borrowing as mutable with prior immutable references

Hey all, I'm trying to write a program to go through a zip archive and parse all JSON files, returning an error if one occurs. My current code looks like this:

fn main() -> Result<(), String> {
    let path = PathBuf::from("file.zip");

    let file = File::open(&path).unwrap();
    let mut archive = zip::ZipArchive::new(file).unwrap();

    for i in 0..archive.len() {
        let mut zip_file = archive.by_index(i).unwrap();

        let zip_file_path = zip_file.enclosed_name().unwrap();

        if !zip_file_path.ends_with(".json") {
            continue;
        }

        let mut buffer = String::new();
        // cannot borrow `zip_file` as mutable because it is also borrowed as immutable
        zip_file.read_to_string(&mut buffer).map_err(|e| {
            format!("Error reading file {}: {}", zip_file_path.display(), e)
        })?;

        let json = serde_json::from_str::<String>(&buffer).map_err(|e| {
            format!("Error parsing file {}: {}", zip_file_path.display(), e)
        })?;

        // Continue using json...
    }

    Ok(())
}

My main issue here is making use of the zip_file_path variable. When I use it, I can a "cannot borrow zip_file as mutable because it is also borrowed as immutable" error, but when I replace usages of the variable with the value itself, the error no longer appears, i.e.

fn main() -> Result<(), String> {
    let path = PathBuf::from("file.zip");

    let file = File::open(&path).unwrap();
    let mut archive = zip::ZipArchive::new(file).unwrap();

    for i in 0..archive.len() {
        let mut zip_file = archive.by_index(i).unwrap();

        let zip_file_path = zip_file.enclosed_name().unwrap();

        if !zip_file_path.ends_with(".json") {
            continue;
        }

        let mut buffer = String::new();
        // cannot borrow `zip_file` as mutable because it is also borrowed as immutable
        zip_file.read_to_string(&mut buffer).map_err(|e| {
            format!("Error reading file {}: {}", zip_file.enclosed_name().unwrap().display(), e)
        })?;

        let json = serde_json::from_str::<String>(&buffer).map_err(|e| {
            format!("Error parsing file {}: {}", zip_file.enclosed_name().unwrap().display(), e)
        })?;

        // Continue using json...
    }

    Ok(())
}

I'd much prefer having a variable I can use to pass into my errors, so I'm wondering what the idiomatic solution to my problem is. Thanks in advance for taking the time to help!

This line returns an &Path which points to memory within the ZipFile structure. This prevents you taking an exclusive (&mut) reference to zip_file because the compiler can't guarantee that the target bytes of zip_file_path won't change.

To fix this, you need to make your own copy of the path, so that it unlocks zip_file:

       let zip_file_path = zip_file.enclosed_name().unwrap().to_path_buf();
1 Like