Could returning an immutable reference from a mutable receiver be unsound?

I was reading this forum post from 3 years ago about returning an immutable reference from a function where the receiver is mutable. Currently rust does not support this. But I wanted to understand if there is an underlying reason, or if it has just not been implemented yet? Are there circumstances where doing this could be unsound?

Essentially you would just borrow as mutable for the life of the function but then the return value would just be an immutable reference. This would let you create function like fn push(&mut self, item: T) -> &T. If you tried to create that today, Rust will require that the mutable borrow lives at least as long as &T, meaning you can't call any more function on self.

See the discussion and links in this recent post, e.g. the overview here. TL;DR -- changing the current behavior would make a lot of things unsound. Adding on some new (explicit) capability is a possibility as far as I'm aware.

1 Like

In that same post, I came up with an example that would be unsound if the longer-lasting borrow wasn't considered mutable.


While this seems to suggest that the reason for not doing this is "merely" backwards compatibility, do you see any way in which the aforementioned code snippet of mine could be made sound, were the mutable borrow not considered to last? I don't think I can express that requirement without actually putting a mutable borrow into the UselessRef, which in reality might not be possible at all, and which could be the very point of using unsafe in a similar situation.

1 Like

I don't, and agree the pattern in general is useful, not just merely backwards compatible. I find it useful beyond (memory) safety to boot; most recently I had a struct that consisted of many overlapping shared references (so returning &muts was not an option) with some interior mutability, and only allowed creation of the struct from &mut self so I could maintain invariants despite the interior mutability. I.e. I needed the struct to be an exclusive borrow for logical reasons. Cell::from_mut comes to mind, too.

2 Likes

Thank you for all the answers. Correct me if I am wrong, but because of patterns like @H2CO3 showed, this can't be changed in the current rust due to code relying on this behavior. However, If new syntax/semantics were introduced (something like &'a mut_downgrade T) then this could be added to the the language. Both behaviors could co-exist. There is is nothing inherently unsafe in downgrading a mut ref. If rust had that from 1.0, then it would have disallowed certain patterns (like UselessCell example) but allowed other patterns that are currently unsupported.

Modulo phrasing, that's how I understand things, yes. As noted in the other thread, you can (unergonomically) work around it.

struct TMIVec<T>(Vec<T>);
impl<T> TMIVec<T> {
    fn push(&mut self, last: T) -> (&T, &Vec<T>) {
        self.0.push(last);
        let last = self.0.last().unwrap();
        (last, &self.0)
    }
}
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.