How do I return a reference to something inside a RefCell without breaking encapsulation?
Asked Answered
N

5

63

I have a struct that has inner mutability.

use std::cell::RefCell;

struct MutableInterior {
    hide_me: i32,
    vec: Vec<i32>,
}
struct Foo {
    //although not used in this particular snippet,
    //the motivating problem uses interior mutability
    //via RefCell.
    interior: RefCell<MutableInterior>,
}

impl Foo {
    pub fn get_items(&self) -> &Vec<i32> {
        &self.interior.borrow().vec
    }
}

fn main() {
    let f = Foo {
        interior: RefCell::new(MutableInterior {
            vec: Vec::new(),
            hide_me: 2,
        }),
    };
    let borrowed_f = &f;
    let items = borrowed_f.get_items();
}

Produces the error:

error[E0597]: borrowed value does not live long enough
  --> src/main.rs:16:10
   |
16 |         &self.interior.borrow().vec
   |          ^^^^^^^^^^^^^^^^^^^^^^ 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 | /     pub fn get_items(&self) -> &Vec<i32> {
16 | |         &self.interior.borrow().vec
17 | |     }
   | |_____^

The problem is that I can't have a function on Foo that returns a borrowed vec, because the borrowed vec is only valid for the lifetime of the Ref, but the Ref goes out of scope immediately.

I think the Ref must stick around because:

RefCell<T> uses Rust's lifetimes to implement 'dynamic borrowing', a process whereby one can claim temporary, exclusive, mutable access to the inner value. Borrows for RefCell<T>s are tracked 'at runtime', unlike Rust's native reference types which are entirely tracked statically, at compile time. Because RefCell<T> borrows are dynamic it is possible to attempt to borrow a value that is already mutably borrowed; when this happens it results in task panic.

Now I could instead write a function like this that returns the entire interior:

pub fn get_mutable_interior(&self) -> std::cell::Ref<MutableInterior>;

However this potentially exposes fields (MutableInterior.hide_me in this example) that are really private implementation details to Foo.

Ideally I just want to expose the vec itself, potentially with a guard to implement the dynamic borrowing behavior. Then callers do not have to find out about hide_me.

Numbers answered 1/4, 2015 at 22:2 Comment(0)
E
34

You can create a new struct similar to the Ref<'a,T> guard returned by RefCell::borrow(), in order to wrap this Ref and avoid having it going out of scope, like this:

use std::cell::Ref;

struct FooGuard<'a> {
    guard: Ref<'a, MutableInterior>,
}

then, you can implement the Deref trait for it, so that it can be used as if it was a &Vec<i32>:

use std::ops::Deref;

impl<'b> Deref for FooGuard<'b> {
    type Target = Vec<i32>;

    fn deref(&self) -> &Vec<i32> {
        &self.guard.vec
    }
}

after that, update your get_items() method to return a FooGuard instance:

impl Foo {
    pub fn get_items(&self) -> FooGuard {
        FooGuard {
            guard: self.interior.borrow(),
        }
    }
}

and Deref does the magic:

fn main() {
    let f = Foo {
        interior: RefCell::new(MutableInterior {
            vec: Vec::new(),
            hide_me: 2,
        }),
    };
    let borrowed_f = &f;
    let items = borrowed_f.get_items();
    let v: &Vec<i32> = &items;
}
Ejaculate answered 1/4, 2015 at 22:19 Comment(2)
Is this the only/idiomatic way to do this? Seems like a bit of trouble... Though I suppose instead of a getItems() method, you could borrow the internals in a block directly where it would then go out of scope (or something...)Kneepad
@Norcalli In the specific case of RefCell, the object needs to be notified when the reference goes out of scope (that's what the destructor of Ref does). Here, we need to preserve this behavior (the error of the OP was due to the Ref instance being dropped too early), and thus encapsulate it.Ejaculate
A
49

Instead of creating a brand new type, you can use Ref::map (since Rust 1.8). This has the same result as Levans' existing answer:

use std::cell::Ref;

impl Foo {
    pub fn get_items(&self) -> Ref<'_, Vec<i32>> {
        Ref::map(self.interior.borrow(), |mi| &mi.vec)
    }
}

You can also use new features like impl Trait to hide the Ref from the API:

use std::cell::Ref;
use std::ops::Deref;

impl Foo {
    pub fn get_items(&self) -> impl Deref<Target = Vec<i32>> + '_ {
        Ref::map(self.interior.borrow(), |mi| &mi.vec)
    }
}
Ance answered 14/7, 2018 at 17:49 Comment(4)
What about instead of get_item, if you were implementing the std::ops::Index<> trait, which requires you to return &Self::Output . Returning a std::cell::Ref, as far as I have been able to figure out, won't satisfy the trait requirement. Is there a way to do interior mutability with for that trait?Afraid
Actually I found a way to do it with UnsafeCell, so I think maybe that will be good enough.Afraid
@Afraid Implementing Index trait to return a value that is not a reference. I wouldn't trust the UnsafeCell implementation because it's quite likely that it introduces memory unsafety.Ance
Note: map doesn't exist for Mutex meaning that this kind of thing is single-thread only (and from glancing at the code in both cell.rs and mutex.rs I think MutexGuard at a minimum would need to be redesigned to support such). I think that it's possible to use a variant of @Levan's solution below in that case, but haven't tried it.Mariettemarigold
E
34

You can create a new struct similar to the Ref<'a,T> guard returned by RefCell::borrow(), in order to wrap this Ref and avoid having it going out of scope, like this:

use std::cell::Ref;

struct FooGuard<'a> {
    guard: Ref<'a, MutableInterior>,
}

then, you can implement the Deref trait for it, so that it can be used as if it was a &Vec<i32>:

use std::ops::Deref;

impl<'b> Deref for FooGuard<'b> {
    type Target = Vec<i32>;

    fn deref(&self) -> &Vec<i32> {
        &self.guard.vec
    }
}

after that, update your get_items() method to return a FooGuard instance:

impl Foo {
    pub fn get_items(&self) -> FooGuard {
        FooGuard {
            guard: self.interior.borrow(),
        }
    }
}

and Deref does the magic:

fn main() {
    let f = Foo {
        interior: RefCell::new(MutableInterior {
            vec: Vec::new(),
            hide_me: 2,
        }),
    };
    let borrowed_f = &f;
    let items = borrowed_f.get_items();
    let v: &Vec<i32> = &items;
}
Ejaculate answered 1/4, 2015 at 22:19 Comment(2)
Is this the only/idiomatic way to do this? Seems like a bit of trouble... Though I suppose instead of a getItems() method, you could borrow the internals in a block directly where it would then go out of scope (or something...)Kneepad
@Norcalli In the specific case of RefCell, the object needs to be notified when the reference goes out of scope (that's what the destructor of Ref does). Here, we need to preserve this behavior (the error of the OP was due to the Ref instance being dropped too early), and thus encapsulate it.Ejaculate
P
5

You can wrap the Vec in an Rc.

use std::cell::RefCell;
use std::rc::Rc;

struct MutableInterior {
    hide_me: i32,
    vec: Rc<Vec<i32>>,
}
struct Foo {
    interior: RefCell<MutableInterior>,
}

impl Foo {
    pub fn get_items(&self) -> Rc<Vec<i32>> {
        self.interior.borrow().vec.clone() // clones the Rc, not the Vec
    }
}

fn main() {
    let f = Foo {
        interior: RefCell::new(MutableInterior {
            vec: Rc::new(Vec::new()),
            hide_me: 2,
        }),
    };
    let borrowed_f = &f;
    let items = borrowed_f.get_items();
}

When you need to mutate the Vec, use Rc::make_mut to obtain a mutable reference to the Vec. If there are still other Rcs referring to the Vec, make_mut will dissociate the Rc from the other Rcs, clone the Vec and update itself to refer to that new Vec, then give you a mutable reference to it. This ensures that the value in the other Rcs doesn't suddenly change (because Rc by itself doesn't provide interior mutability).

Philipp answered 15/7, 2018 at 6:8 Comment(0)
G
1

Ref<X> is the usual answer. However, sometimes, such as when designing a trait, a consistent interface is required. For instance a trait with:

fn get_x(&self) -> &X

will preclude structs storing x in a RefCell<X>, while:

fn get_x(&self) -> Ref<X>

will preclude structs storing x as a plain X.

A pattern that can be used to get around this is to instead take a method which operates on the data.

fn with_x(&self, fun : &dyn FnOnce(&X));

Which works with both classes using X and RefCell<X> (or any other composition for that matter):

fun(&self.x);
// or
fun(&self.x.borrow());
Granulite answered 28/3, 2023 at 15:42 Comment(0)
T
1

If you really must return a reference to the data (as in an external crate requires some interface or similar) you can do so with Rc::leak, which currently requires nightly, until it stabilizes you can help yourself with this little function:

use std::cell::Ref;
fn leak_ref<'a, T>(orig: Ref<'a, T>) -> &'a T {
    Box::leak(Box::new(orig))
}

The same disclaimer as for the original leak applies here to:

The underlying RefCell can never be mutably borrowed from again and will always appear already immutably borrowed. It is not a good idea to leak more than a constant number of references. The RefCell can be immutably borrowed again if only a smaller number of leaks have occurred in total.

Tamie answered 1/8, 2023 at 12:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.