Struct storing and returning shared vs. mutable references

⚓ Rust    📅 2025-08-06    👤 surdeus    👁️ 6      

surdeus

Heya!

(First post, please be kind)

I am - desperately - trying to understang why Case 1 works

pub struct Thing<'a> {
    data: &'a Data,
}

impl<'a> Thing<'a> {
    pub fn get(&self) -> &'a Data {
        self.data
    }
}

But Case 2 doesn't:

pub struct Thing<'a> {
    data: &'a mut Data,
}

impl<'a> Thing<'a> {
    pub fn get(&mut self) -> &'a mut Data  {
        self.data
    }
}

Using common-sense, my conclusion is as follows

  • Case 1:

    • We know, Thing borrows Data, that must outlive Thing.
    • So it's fine to hand-out shared references that are valid
      • even after Thing goes out of scope
      • but not longer than Data's scope
  • Case 2:

    • Thing holds a mutable borrow of Data.
    • Access to Data must go through Thing.
    • Lifetime 'a must be longer that the anonymous lifetime of &mut self.
    • If that case was allowed, we could hand-out multiple mutable references to Data which is bad

Now I am wondering how Rust thinks about this & accepts Case 1 but comes to the conclusion that Case 2 can cause problems:

22 | impl<'a> Thing<'a> {
   |      -- lifetime `'a` defined here
23 |     pub fn get(&mut self) -> &'a mut Data  {
   |                - let's call the lifetime of this reference `'1`
24 |         self.data
   |         ^^^^^^^^^ method was supposed to return data with lifetime `'a` but it is returning data with lifetime `'1`
  1. I do believe Case 1 is merely solved by the Copy implementation of shared references? So copying a &'a Data gives a &'a Data. Simple enough.
  2. I think to understand that Case 2 error, I first need to understand what self.data is. Is it an (implicit) re-borrow that "somehow" ties the anonymous lifetime of &mut self to &'a mut Data ... but then realizes 'a is not a subtype of the anonymous lifetime?
  3. If 2) was the root-cause of the smartness of the compiler, are there different re-borrow rules for shared and mutable references? Because making Case 1 an explicit re-borrow is still working fine:
pub struct Thing<'a> {
    data: &'a Data,
}

impl<'a> Thing<'a> {
    pub fn get(&self) -> &'a Data {
        &*self.data
    }
}

Any pointers what documentation or reference explains how Rust actually comes to the conclusion that Case 2 is bad would be highly appreciated as well, Thank You!

1 post - 1 participant

Read full topic

🏷️ Rust_feed