Refcell and avoiding exposing Ref in the api

I am trying to return a value contained in a RefCell inside a struct (playground) but getting an error ("returns a value referencing data owned by the current function").

use std::{cell::RefCell, ops::Deref};

struct Registry {
    value: RefCell<String>
}

impl Registry {
    fn get(&self) -> &String {
        self.value.borrow().deref()
    }
}

I understand that this is because (from the RefCell.borrow() docs) "The borrow lasts until the returned Ref exits scope".

So if I change get to return a Ref:

    fn get(&self) -> Ref<'_, String> {
        self.value.borrow()
    }

It works. But I am not sure returning a Ref from this API is a good idea (feels like it is exposing the fact that I am using a RefCell). Is there a way to keep internal mutability an internal concept?

Edit: Fixed the return value from Ref<_', u32> to Ref<_', String>.

You can make a newtype wrapper around Ref, with private field, which will Deref to the contained value (String, in your example, or, probably better, &str):

struct StringRef<'a>(Ref<'a, String>);

impl Deref for StringRef<'_> {
    type Target = str;
    fn deref(&self) -> &str {
        &*self.0
    }
}

impl Registry {
    fn get(&'_ self) -> StringRef<'_> {
        StringRef(self.value.borrow())
    }
}

Playground

2 Likes

Thanks. However, I am not quite sure what the newtype wrapper buys over simply exposing Ref. The following program works (I took main from your playground)

use std::{cell::{Ref, RefCell}, ops::Deref};

struct Registry {
    value: RefCell<String>
}

impl Registry {
    fn get(&self) -> Ref<'_, String> {
        self.value.borrow()
    }
}

fn main() {
    let registry = Registry { value: RefCell::new(String::from("value")) };
    let value = registry.get();
    println!("{}", value.to_ascii_uppercase());
}

The wrapper type gives you the ability to change the implementation from RefCell to something else, without breaking downstream code. This may or may not be something you care about.

If you want to avoid any sort of Ref-like wrapper, you might be able to use a closure-based API like this:

impl Registry {
    fn with(&self, mut f: impl FnMut(&str)) {
        let value = self.value.borrow();
        f(&value);
    }
}

fn main() {
    let registry = Registry { value: RefCell::new(String::from("value")) };
    registry.with(|value| {
        println!("{}", value.to_ascii_uppercase());
    });
}
2 Likes

It hides your implementation details (e.g. the fact that you are using RefCell) and allows you to change internals of your project without changing the return type of Registry::get: you can just change the implementation of StringRef::deref.

(Apparently I posted the same answer as above with seconds of interval.)

But if you move the Registry into its own module (simulating the difference between your crate and user crate), it no longer works:

mod registry {

    use std::cell::RefCell;

    pub struct Registry {
        value: RefCell<String>,
    }
}

use registry::Registry;
use std::cell::RefCell;

fn main() {
    let _ = Registry {
        value: RefCell::new(String::from("value")),
    };
}

Error:

error[E0451]: field `value` of struct `Registry` is private
  --> src/main.rs:15:9
   |
15 |         value: RefCell::new(String::from("value")),
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ private field

Thanks for your responses.

I will have to think about this more about this. Changing implementation from RefCell to something that can have a Deref implementation is unlikely in my situation, so not sure if I should introduce an extra level of indirection and extra code just yet.