Search code examples
rustlifetimeborrowing

How to fit a Rust ZipArchive and ZipFile into a single Read-implementing struct, while dealing with lifetime and borrowing rules


This question could be asked in a few different ways but my end-goal is to encapsulate one file from a ZipArchive (Rust zip crate) into a struct called ZipReader that implements Read, like so:

struct ZipReader<R: Read + Seek> { ... }
impl<R: Read + Seek> ZipReader<R> {
    fn from(src: R, filename: &str) -> ZipReader<R> { ... }
}
impl <R: Read + Seek> Read for ZipReader<R> { ... }

My effort to do so has involved constructing a new ZipArchive from the src stream, then using ZipArchive.by_name(name) to extract the ZipFile<'a> from which bytes may be read.

The issue I run into is that the ZipFile<'a> struct references the lifetime of the &'a mut ZipArchive from which it was constructed -- meaning the ZipArchive must remain in scope and remain mutably borrowed for the lifetime of the ZipFile.

This combination of constraints seems to make it impossible to encapsulate the zip in a new struct:

pub struct ZipReader<R: Read + Seek> {
    file: zip::read::ZipFile<'_>, // Have experimented with lifetimes here with no success
    archive: ZipArchive<R>,
}
impl <R: Read + Seek> ZipReader<R> {
    fn from(mut zip: R, name: &str) -> ZipReader<R> {
        let archive = ZipArchive::new(zip);
        let file = archive.by_name(name).unwrap();
        ZipReader {
            file: file,
            archive: archive.unwrap(),
        }
    }
}
impl <R: Read + Seek> Read for ZipReader<R> {
    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
        self.file.read(buf)
    }
}

I cannot move file alone into the returned ZipReader because archive will go out of scope, but I cannot move archive to ZipReader because it is borrowed by file.

It seems like modifying the zip crate and incorporating the ZipFile state into ZipArchive to eliminate the lifetime issue would be one way to solve the problem. How could this be solved without modifying anyone else's code?


Solution

  • As Coder-256 mentioned, owning_ref::OwningHandle provides functionality to solve the borrowing problem, but not the lifetime problem. There is an open issue on the repo for owning_ref that describes an equivalent situation and requests additional functionality to solve it, but it has been open since 2017 with no resolution.

    I ended up modifying the zip crate to transfer ownership of the underlying stream instead of holding the reference to the ZipArchive. This is satisfactory enough for my purposes, but a solution like owning_ref seems to be the correct approach for this type of problem.

    Update: It seems the ouroboros crate solves this problem in a more generic way, see: Open a single file from a ZIP archive and pass on as Read instance