How to return reference to value in Rc or RefCell

This is an interesting case to think about, really. Maybe this will lend some insight (I hope so).

First a couple basics that are pretty trivial.

This doesn't compile:

fn borrow_from_owned<T>(rc: Rc<T>) -> &T {
    &*rc
}

The compiler doesn't like that the lifetime of the returned reference "came out of nowhere", which is already a big clue. We can tell it, hey, don't mind that:

fn borrow_from_owned<'any, T>(rc: Rc<T>) -> &'any T {
    &*rc
}

But it will still fail, because you can't borrow from a local and return a reference to it. And that's definitely correct here too! If that rc is the only one still pointing at the contents, the contents will drop when rc drops. Good, fine, makes sense.


This compiles just fine:

fn borrow_from_borrow<'rc, T>(rc: &'rc Rc<T>) -> &'rc T {
    &*rc
}

It's very much like a field access, or borrowing a &str from a &String. Good, fine, makes sense.


Now the interesting one. Knowing how Rc works, you may expect this to work:

fn borrow_from_clone<'rc, T>(rc: &'rc Rc<T>) -> &'rc T {
    let clone = Rc::clone(rc);
    &*clone
}

And as was explained, the compiler doesn't understand that the returned reference is just fine, because it really pointing at someting owned by rc. Let's take it in slow motion and point out a few reasons why it "has" to be this way (without Rc becoming very, very special-cased by the language).

First, let's be a little more explicit about the clone type.

fn borrow_from_clone<'rc, T>(rc: &'rc Rc<T>) -> &'rc T {
    let clone: Rc<T> = Rc::clone(rc);
    &*clone
}

That Rc<T> of clone has no lifetime on it. There's nothing on the language level still connecting it to rc. This is similar to the first case, where a lifetime is going to have to come "out of nowhere".

Let's imagine there's a way to overcome that though, and be more explicit about the returned value too. Maybe the compiler will get that they're related.

fn borrow_from_clone<'rc, T>(rc: &'rc Rc<T>) -> &'rc T {
    let clone: Rc<T> = Rc::clone(rc);
    let borrow: &'rc T = &*clone;
    borrow
}

Nope, it's not buying it. Here, we get a whole new error about 'rc being too long. This comes up occasionally in other situations: When you have a function with a lifetime parameter, that lifetime is chosen by the caller of the function. Within the function, it could be almost anything -- the main thing you know is that it lasts longer than the function body (it lasts at least as long as the call site expression). [1]

But clone is a local, so you can't borrow it for longer than the function body.

Rust generally wont just take our word that things are okay. You make assertions like that by using unsafe and taking on the responsibilities to uphold Rust's safety guarantees yourself.


Finally, I'd like to outline another way to think of this situation. Disclaimer: it's a bit more hand-wavey than the rest, and it's possible Rust will find ways to grow beyond this mental model. Anyway, the general idea is to think of a chain of exclusive access.

First note that shared references are very flexible -- you can Copy them, so you can even copy a &'long T out of a &'short &long T, and so forth. For example, if I have a &String, and then I create some &str from that, and then I use the &String again, I can keep on using the &str. No problem.

The exclusive nature of &mut make it much more rigid, though. You can only move or reborrow them, not copy them, and you cannot get a &'long mut T from a &'short mut &'long mut T. And if I have a &mut Vec<T>, and I create a &mut [T] or even &[T] from that, and then I use the &mut Vec<T> again... my &[T] sub-borrow gets "cancelled". This makes sense -- maybe my use of &mut Vec<T> was to call .clear() after all.

And the same is true if you have a &T you got from an owned value, and then use the owned value again. You can do anything with owned values that you can with a &mut T and more, after all. The semantics for ownership are at least as strong.

Beyond a single borrow, a chain of exclusive access back to the owned, borrowed object must remain "unbroken".

let mut owned = String::new();
let a = &mut owned;
let b = &mut *a;
let c = &mut *b;
let d = &mut *c;

// This use of `b` makes both `c` and `d` unusable.
// But `b` is still ok to use,
// - and so is `a` (but using it makes `b` unusable),
// - and so is `owned` (but using it makes all borrows unusable)
println!("{b}");

So with that in mind, consider this function:

fn borrow_unique(i: &mut Holder<i32>) -> &mut i32 {
    let mut h: Holder<&mut i32> = Holder(&mut i.0);
    let h = &mut h;
    
    &mut *h.0
}

It fails, while the shared version succeeds. We can talk about why in terms of lifetimes and borrowing and copying shared references -- we've created some &'local mut Holder<&'foreign i32> value, and you can't get a &'long mut T from inside a &'short mut &'long mut T and so on.

But we can also reason about it in terms of the chain of exclusive access. When our local goes out of scope at the end of this function, we break the chain of exclusive access -- an intermediate link is invalid, so our returned value has no path back to the owned value, and it too must be invalid.

With that viewpoint, you can forget about lifetimes per se and see how borrow_from_clone is analogous to borrow_unique in some way: you stuck the data behind something the language considers exclusive (an owned Rc) and then borrowed through that exclusive thing. Once the exclusive thing is gone, so is your access.


  1. The lifetimes also have to make sense for the types, so here: fn foo<'a, 'b>(_: &'a &'b str) {} you know that 'b: 'a. ↩︎

9 Likes