Strange lifetime issue with reference to another struct member

I'm having trouble with lifetime annotations. I hope this hasn't been covered a million times, but I couldn't really find anything helpful.

I have the following code:

use pdfium_render::prelude::{PdfDocument, Pdfium};

struct X<'a> {
    docs: Vec<PdfDocument<'a>>,
    pdfium: Pdfium
}

impl<'a> X<'a> {
    fn new() -> Self {
        let pdfium = Pdfium::new(Pdfium::bind_to_system_library().unwrap());
        Self {
            docs: vec![],
            pdfium
        }
    }
    fn test(&'a mut self) {
        let doc = self.pdfium.load_pdf_from_file("test.pdf", None).unwrap();
        self.docs.push(doc);
    }
}

struct Y<'a> {
    docs: Vec<&'a i32>,
    pdfium: i32,
}

impl<'a> Y<'a> {
    fn new() -> Self {
        let pdfium = Pdfium::new(Pdfium::bind_to_system_library().unwrap());
        Self {
            docs: vec![],
            pdfium: 123
        }
    }
    fn test(&'a mut self) {
        let doc = &self.pdfium;
        self.docs.push(doc);
    }
}

struct Z<'a> {
    docs: Vec<PdfDocument<'a>>,
    pdfium: &'a Pdfium
}


impl<'a> Z<'a> {
    fn new(pdfium: &'a Pdfium) -> Self {
        Self {
            docs: vec![],
            pdfium
        }
    }
    fn test(&mut self) {
        let doc = self.pdfium.load_pdf_from_file("test.pdf", None).unwrap();
        self.docs.push(doc);
    }
}

fn main() {
    let mut x = X::new();
    x.test();  // Error

    let mut y = Y::new();
    y.test();
    y.test(); // Error (works with a single y.test() call)

    let pdfium = Pdfium::new(Pdfium::bind_to_system_library().unwrap());
    let mut z = Z::new(&pdfium);
    z.test();
    z.test(); // Works
}

I get the following compilation error:

error[E0597]: `x` does not live long enough
  --> src/main.rs:62:5
   |
62 |     x.test();  // Error
   |     ^^^^^^^^ borrowed value does not live long enough
...
72 | }
   | -
   | |
   | `x` dropped here while still borrowed
   | borrow might be used here, when `x` is dropped and runs the destructor for type `X<'_>`

error[E0499]: cannot borrow `y` as mutable more than once at a time
  --> src/main.rs:66:5
   |
65 |     y.test();
   |     -------- first mutable borrow occurs here
66 |     y.test(); // Error
   |     ^^^^^^^^
   |     |
   |     second mutable borrow occurs here
   |     first borrow later used here

For completeness, here is the relevant code from PDFium:

    pub fn load_pdf_from_file<'a>(
        &'a self,
        path: &(impl AsRef<Path> + ?Sized),
        password: Option<&'a str>,
    ) -> Result<PdfDocument, PdfiumError> {
        self.load_pdf_from_reader(File::open(path).map_err(PdfiumError::IoError)?, password)
    }
    pub fn load_pdf_from_reader<'a, R: Read + Seek + 'a>(
        &'a self,
        reader: R,
        password: Option<&'a str>,
    ) -> Result<PdfDocument<'a>, PdfiumError> {
...

As far as I can tell, the issue is that I can only do a single &'a mut borrow, so for X the second borrow occurs in the destructor, whereas for Y, I can do a single y.test() call because the destructor doesn't use anything with lifetime 'a (note that PdfDocument<'a> has a nontrivial destructor)?

My Z implementation works, but it seems inelegant to have the Pdfium object outside. Is there a way to implement this without having to pass Pdfium as a reference to Z?

This seems to be the combination of two anti-patterns:

  1. &'a mut T<'a> means "borrow the value for the rest of its validity" (since mutable references are invariant in the lifetime of the pointed type). Thus, such a mutable borrow renders the borrowed value completely useless and it's almost never what you want.
  2. Trying to store a reference to another field in the struct doesn't make sense: it would be invalidated and become dangling as soon as the struct is moved, so it simply doesn't (practically) work in safe Rust.

It's not, and it is exactly what you should be doing.

1 Like

There's actually quite a handful of topics and information on this kind of problem (i. e. the attempt to combine fields in one struct where one contains a reference to or into the other) - I'd look up some links if I had more time right now, but as a starting point e. g. your own phrasing "reference to another struct member" (plus the keyword "rust") does give me some decent search engine results; since you talk about having searched for information unsuccessfully, I'm actually a bit curious about what you did search for. As this kind of issue is a re-occurring theme, I wonder whether there are any natural keywords one would look for that don't lead to good answers, and if we can perhaps do something about that.

1 Like

Sorry, my issue was that I didn't initially realize that the reference to another struct member was the problem and then only figured that out when writing the post, so I didn't search for it and didn't think of searching for it when I changed the title.

But you are right, there are useful results with those keywords.

No worries, I was just curious. I know the effect that writing down your thoughts or problems can have quite well; sometimes the problems even entirely solve themselves from the more methodical thought process you need to apply when formulating a post asking for help.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.