Lifetime question: object keeps borrowed

I've created 2 structs Decompressor and Toc like below:

pub struct Decompressor<'a> {
    pub options: &'a Options,
    pub toc: Option<Toc<'a>>,
    pub file: File,
}

impl<'a> Decompressor<'a> {
    pub fn new(options: &'a Options) -> Result<Decompressor<'a>, std::io::Error> {
        let mut file = File::open(&options.input.first().unwrap())?;
        Ok(Decompressor {
            options,
            toc: None,
            file,
        })
    }

    pub fn read_toc(&'a mut self) -> Result<(), std::io::Error> {
        let mut header = Header::new(&mut self.file, self.options, 0)?;
        let mut data = header.read().unwrap();
        self.toc = Some(header.decode(&mut data).unwrap());
        Ok(())
    }
}

pub struct Header<'a> {
    pub file: &'a mut File,
    pub options: &'a Options,
    pub header_file_pos: u64,
}

impl<'a> Header<'a> {
    pub fn new(
        zar_file: &'a mut File,
        options: &'a Options,
        header_file_pos: u64,
    ) -> std::io::Result<Header<'a>> {
        Ok(Header {
            file: zar_file,
            options: options,
            header_file_pos,
        })
    }

    pub fn decode(&self, data: &Vec<u8>) -> Result<Toc, std::io::Error> {
        let mut toc = Toc::new();
        toc.decode(data)?;
        Ok(toc)
    }
}

Rust is complaining in function read_toc that header is borrowed and does not live long enough (line self.toc = Some(header.decode(&mut data).unwrap());).
I do not understand why header keeps borrowed, because function decode returns a new Toc object, which does not have any dependencies to header.

Can you include the definition of Toc? From the definition of Decompressor, it seems like it includes a reference to external data, and the lifetime of that reference is getting tied to the lifetime of Header because of how the inference rules work. Adding the lifetimes explicitly gives this signature for decode:

pub fn decode<‘a>(&’a self, data: &’a Vec<u8>) -> Result<Toc<‘a>, std::io::Error>;

I suspect that you really want Toc to only capture the lifetime of data, which is represented like this:

pub fn decode<‘a>(&self, data: &’a Vec<u8>) -> Result<Toc<‘a>, std::io::Error>;

As a side note, I’m having trouble tracking what data is supposed to live where. I recommend that you consider replacing some of these references with either Arcs for shared ownership or cloned copies for full ownerhsip. Options particularly feels like something that’s unlikely to benefit much from being borrowed instead of owned.

You have a self-referential struct: decompressor.toc contains File that sits in the decompressor. Rust's borrow checker can't guarantee safety of this.

Rust references (temporary borrows) are not general-purpose method of referencing objects as you'd in Java or in C with pointers. By design, they can't be used to store anything "by reference". They can't form mutable parent-child relationships and come with very specific semantics of making things temporary, locked and bound to a scope. If you don't intend to make things unmovable and restrictive, don't use references, especially don't use references in structs.

If you need parent-child relationships, use Arc<Mutex<T>> rather than &mut T. But usually Rust idiomatic code avoids parent-child relationships in data. You can pass the parent to methods explicitly where it's needed, or rearrange data so that the parent is not needed.

Try not using references in structs at all. If you want to store something by reference, the correct type for that is Box or Arc.

Never use &Vec<T>. That type doesn't make sense (you're asking for growable vector, but then you're asking for immutable access that prevents it from growing). In function arguments use &[T].

3 Likes