Borrowing error when downgrading `&mut T` to `&T`

Ahem, I am seeing a borrow-checker error which I don't entirely understand:

struct Holder {
    inner: String,
}

impl Holder {
    fn weird(&mut self) -> &str {
        self.inner = "hello".to_string();
        &self.inner
    }
}

fn f(mut h: Holder) {
    let s1 = h.weird();
    let s2 = h.inner.as_str();
    println!("{} {}", s1, s2)
}

The key here is fn weird(&mut self) -> &str -- it takes a &mut reference in, and emits a & reference. But it seems that it is essentially treated as fn weird(&mut self) -> &mut str by the borrow checker -- in the f, the h remains frozen after call to weird, so accessing it, even via & , is impossible.

Two questions:

Is this a limitation of a borrow checker, or is the problem fundamental? To me, it seems like the above code should work, regardless of what's happening inside weird. What does the stacked borrows model say about this code?

What are the practical work-arounds here? In the actual problem, the weird inserts a key/value into a map and returns a shared reference to the value:

    let mut hm = HashMap::new();
    hm.insert(1, "hello".to_string());

    let w = &*hm.entry(2).or_insert("world".to_string());
    let h = &hm[&1];
    println!("{} {}", h, w);

I can work-around that by doing a second lookup, but that's not elegant:

    let mut hm = HashMap::new();
    hm.insert(1, "hello".to_string());

    hm.entry(2).or_insert("world".to_string());
    let w = &hm[&2];
    let h = &hm[&1];
    println!("{} {}", h, w);

I think this limitation is fundamental if you want to use lifetimes as markers for safety in unsafe code. Consider for instance the following useless Cell type:

struct UselessCell<T> {
    inner: T,
}

impl<T> UselessCell {
    fn borrow_mut(&mut self) -> UselessRef<'_, T> {
        UselessRef {
            inner: &mut self.inner as *mut T,
        }
    }
}

struct UselessRef<'lt, T> {
    inner: *mut T,
}

impl<'lt, T> UselessRef<'lt, T> {
    fn get(&self) -> &'lt T {
        unsafe { &*self.inner }
    }

    fn mutate(&mut self) where T: Default {
        unsafe {
            *self.inner = T::default();
        }
    }
}

In this scenario, UselessRef only ever gives out immutable borrows, but it can mutate the wrapped value itself. In order for this not to have catastrophic consequences, it has to be assumed that anything with the 'lt lifetime has a mutable borrow if it was generated from a mutable borrow itself.

1 Like

I believe this is this phenomenon.

7 Likes

I agree it's not sufficiently documented. I found out about in this blog post:

For example, people sometimes want the ability to have a variant on insert – basically a function that inserts a T into a collection and then returns a shared reference &T to inserted data. The idea is that the caller can then go on to do other “shared” operations on the map (e.g., other map lookups). So the signature would look a little like this:

impl SomeCollection<T> {
  fn insert_then_get(&mut self, data: T) -> &T {
    //
  }
}

This signature is of course valid in Rust today, but it has an existing meaning that we can’t change. The meaning today is that the function requires unique access to self – and that unique access has to persist until we’ve finished using the return value. It’s precisely this interpretation that makes methods like Mutex::get_mut sound.

1 Like

This is also discussed briefly in the Rustonomicon: Limits of Lifetimes - The Rustonomicon

1 Like

The most reliable / general albeit cumbersome solution is to have weird() yield "the remaining of self" next to the borrowed value:

fn weird(self: &'_ mut Self) -> (&'_ str, &'_ Self) {
    self.inner = …;
    (&self.inner, &self)
}

from there:

fn f(mut h: Holder) {
    let (s1, h) = h.weird();
    let s2 = h.inner.as_str();
    dbg!(s1, s2);
}
3 Likes