Returning immutable references with Option, RefCell

Given that I've got a struct that contains a string, I'd like to add a method that returns an immutable reference to the string. In the simple case, this works:

struct MyVal {
    string: String,
}

impl MyVal {
    fn get(&self) -> &str { &self.string }
}

Fair enough. Now, in my case the string may or may not be present; but suppose it is. (Yeah, I know that calling "unwrap()" like this is bad style. Bear with me.)

struct MyVal {
    string: Option<String>,
}

impl MyVal {
    // Doesn't work; temporary value created.
    fn get(&self) -> &str { &self.string.unwrap().string }
}

The reason why I've got that Option in there is because the string isn't always present; but it can be created on the fly when it's needed using internal mutability, so it will always actually exist when I want it:

struct MyVal {
    string: RefCell<Option<String>>,
}

impl MyVal {
    // Doesn't work; temporary value created.
    fn get(&self) -> &str { 
        // Omitted: ensure sure the string is_some
        // I might not have the syntax quite right for the RefCell, but it doesn't matter
        // because I still have a temporary value.
        &self.string.borrow().unwrap().string 
    }
}

In my actual code I work around this by using RefCell<Option<Rc<String>>> and
explicitly returning Rc<String>, which works well enough; but I'd rather not leak the presence of Rc and just return &str.

It seems like it ought to be doable; the MyVal struct owns the value, and should be able to return an immutable borrow to it, but I don't know how.

1 Like

For the second one you just need to transform the Option with as_ref.

fn get(&self) -> &str {
    self.string.as_ref().unwrap()
}

For the other one it's a little more complicated because you have to return the RefCell's guard.

fn get(&self) -> Ref<str> {
    Ref::map(self.string.borrow(), |borrow| {
        borrow.as_ref().unwrap().as_str()
    })
}
3 Likes

Excellent! Relative newbie here—I have big dreams, but I haven't learned all of the tricks yet.

Thanks very much!

You can also hide the Ref a bit by changing the return type in the signature:

fn get(&self) -> impl Deref<Target = str> + '_ {
...
}
2 Likes

Giving this a try, and while it will probably do, it isn't quite what I was hoping for. When returning Ref (the third case, in either form), this happens:

// Pass as string, because my_value is meant to take ownership without cloning.
let my_value = MyValue::from_string("abc".toString());

// I should be able to say this.
assert_eq!(my_value.get(), "abc");

// But I have to say this:
assert_eq!(&*my_value.get(), "abc");

Is there a way to make it simply look like an &str?

I'm guessing not.....

Once you call get, you can do

let thing = self.get();
let thing: &str = &thing;

From then on you can use thing as a &str, but if you want something inline, I don't think you can do it.

1 Like

Yeah, that's what I thought. Thanks!

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.