Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 991
Drew Avatar asked Apr 01 '15 22:04

Drew


3 Answers

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;
}
like image 105
Levans Avatar answered Nov 15 '22 08:11

Levans


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)
    }
}
like image 39
Shepmaster Avatar answered Nov 15 '22 09:11

Shepmaster


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).

like image 31
Francis Gagné Avatar answered Nov 15 '22 08:11

Francis Gagné