HiveBrain v1.2.0
Get Started
← Back to all entries
snippetrustModerate

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

Submitted by: @import:stackoverflow-api··
0
Viewed 0 times
somethinghowrefcellreturnencapsulationbreakingwithoutinsidereference

Problem

I have a struct that has inner mutability.

use std::cell::RefCell;

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

impl Foo {
    pub fn get_items(&self) -> &Vec {
        &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 {
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 uses Rust's lifetimes to implement 'dynamic borrowing', a process whereby one can claim temporary, exclusive, mutable access to the inner value. Borrows for RefCells are tracked 'at runtime', unlike Rust's native reference types which are entirely tracked statically, at compile time. Because RefCell 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;

Solution

You can create a new struct similar to the Ref 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 {
    guard: Ref,
}


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

use std::ops::Deref;

impl Deref for FooGuard {
    type Target = Vec;

    fn deref(&self) -> &Vec {
        &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 = &items;
}

Code Snippets

use std::cell::Ref;

struct FooGuard<'a> {
    guard: Ref<'a, MutableInterior>,
}
use std::ops::Deref;

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

    fn deref(&self) -> &Vec<i32> {
        &self.guard.vec
    }
}
impl Foo {
    pub fn get_items(&self) -> FooGuard {
        FooGuard {
            guard: self.interior.borrow(),
        }
    }
}
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;
}

Context

Stack Overflow Q#29401626, score: 37

Revisions (0)

No revisions yet.