Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to borrow the T from a RefCell<T> as a reference?

Sometimes I have a struct containing a value which is wrapped in a RefCell, and I want to borrow the value, but I don't want to make the signature of the accessor function to depend on the internal implementation. To make it work, I need to return the reference as a Ref<T> instead of a &T.

For example, if this is my struct:

use std::cell::RefCell;

pub struct Outer<T> {
    inner: RefCell<T>,
}

I could write an accessor like this:

use std::cell::Ref;

impl<T> Outer<T> {
    fn get_inner_ref(&self) -> Ref<T> {
        self.inner.borrow()
    }
}

This works fine. I can use it like this:

fn main() {
    let outer = Outer { inner: RefCell::new(String::from("hi")) };
    let inner: &str = &outer.get_inner_ref();
    println!("inner value = {:?}", inner);
}

However, this exposes Ref as part of the public API, which would make it harder to change the internals later, without breaking backwards compatibility.

If I try change the signature to return an &T — which &Ref<T> can coerce to — then I get lifetime errors:

impl<T> Outer<T> {
    fn get_inner_ref(&self) -> &T {
        &self.inner.borrow()
    }
}

The error is:

error[E0597]: borrowed value does not live long enough
  --> src/main.rs:16:10
   |
16 |         &self.inner.borrow()
   |          ^^^^^^^^^^^^^^^^^^^ temporary value does not live long enough
17 |     }
   |     - temporary value only lives until here
   |
note: borrowed value must be valid for the anonymous lifetime #1 defined on the method body at 15:5...
  --> src/main.rs:15:5
   |
15 | /     fn get_inner_ref(&self) -> &T {
16 | |         &self.inner.borrow()
17 | |     }
   | |_____^

There doesn't appear to be a way to fix that, because the error message is correct. The code is trying to take a reference to the Ref<T>, which only lasts as long as the function call. To make this work, I'd have to move out the Ref<T> itself by returning it — exactly like in the original code above — rather than making a new reference to it.

There is an answer to How do I return a reference to something inside a RefCell without breaking encapsulation? which would technically solve this, but it is a more specialised case (to get only a part of the value in the RefCell) and the solution seems overly complex for this simpler situation.

like image 370
Peter Hall Avatar asked Jan 02 '23 03:01

Peter Hall


1 Answers

This is exactly the purpose of impl Trait, which has been available in stable Rust since version 1.26.

use std::ops::Deref;

impl<T> Outer<T> {
    fn get_inner_ref<'a>(&'a self) -> impl Deref<Target = T> + 'a {
        self.inner.borrow()
    }
}

The Rust compiler knows that the actual implementation is Ref<T> but lets you avoid having to write it explicitly, and callers of this function can only use functionality provided by the Deref trait.

As long as the actual value that you return is of a type that implements Deref<Target = T>, you are free to change that implementation later without breaking any code that uses it. For example, you could return &T or one of several other reference types, including your own custom type, as in the other linked question.

like image 146
Peter Hall Avatar answered Jan 04 '23 23:01

Peter Hall