fn foo<T>(x: &std::cell::RefCell<T>) -> std::cell::Ref<T> {
x.borrow()
}
fn foo2<T>(x: &std::cell::RefCell<std::cell::RefCell<T>>) -> std::cell::Ref<T> {
x.borrow().borrow()
}
foo compiles. foo2 refuses to compile. Why?
Is it possible to get foo2 to compile by adding some 'a, 'b, 'c to the type signature ?
alice
January 1, 2020, 9:36pm
2
It fails because the return of the second borrow
borrows from the Ref
returned from the first borrow. Unfortunately Ref::map
requires you to return a reference, so you can't just make a Ref<Ref<T>>
.
Basically you're trying to return an item along with what its borrowed from.
The next thing I tried is this:
struct MyRef<'a, T> {
inner: Ref<'a, RefCell<T>>,
}
impl<'a, T> Deref for MyRef<'a, T> {
type Target = T;
fn deref(&self) -> &T {
// ... ?
}
}
fn foo2<T>(x: &RefCell<RefCell<T>>) -> Ref<Ref<T>> {
MyRef { inner: x.borrow() }
}
but this does not work as the Ref
from the inner needs to be stored somewhere. This seems to be your best bet:
struct MyRef<'a, T> {
inner: Ref<'a, RefCell<T>>,
}
impl<'a, T> MyRef<'a, T> {
fn borrow(&self) -> Ref<'_, T> {
self.inner.borrow()
}
}
fn foo2<T>(x: &RefCell<RefCell<T>>) -> MyRef<T> {
MyRef { inner: x.borrow() }
}
even though it requires two calls, e.g. foo2(val).borrow()
to get to a single Ref<T>
.
Unfortunately these two calls are required to correctly handle both Ref
s being dropped without a self-referential type.
1 Like
@alice : Thank you for the detailed reply. It sounds like we can't get foo
and foo2
to have the same return Type? Is that correct?
I'm not sure how to rigorously state: "creating an enum with two arms, one for foo, one for foo2, doesn't count."
pub enum Blah<T> {
A(RefCell<T>),
B(RefCell<Box<Blah<T>>>),
}
pub enum MyRerf<T> {
A(Ref<T>),
// what else to add here?
}
impl <T> Blah<T> {
pub fn get_my_ref(&self) -> MyRef<T>
}
impl<T> MyRerf<T> {
pub fn get_ref(&self) -> &T {
match self {
MyRerf::A(r) => &r,
_ => todo!(),
}
}
}
Is it possible to (1) modify MyRef, (2) add some lifetime parameters, and get it so that the above code can compile?
Blah
is either a RefCell or nested RefCells. Then, I want to be able to construct an object MyRef
, and I want MyRef
to be able to get a &T
from.
Nested RefCell
s are a pain to work with, and I suggest you restructure your code to avoid it. Why do you need nested RefCell
?
One solution is to not hand out references, and instead do this:
impl <T> Blah<T> {
pub fn with_ref<R>(&self, f: impl FnOnce(&T) -> R) -> R {
match self {
Self::A(val) => f(&val.borrow()),
Self::B(rec) => rec.borrow().with_ref(f),
}
}
}
But this has it's own problems, most notably it is verbose to work with and hard to read
One way to restructure you code is to change Blah
into this
struct Blahs<T> {
values: RefCell<Vec<BlahInner<T>>>
}
enum BlahInner<T> {
Value(T),
Next(usize)
}
You can then implement get_ref
like so playground
I'm implementing something similar to Jane Street Tech Blog - Breaking down FRP 's
val join: 'a signal signal -> 'a signal
'a signal
ends up being involving a RefCell<T>
The 'a signal signal
ends up being a nested RefCell
Hyeonu
January 2, 2020, 4:53am
8
FRP is all about avoiding shared mutable state. Why did you think you need a RefCell
for it?
Using FRP is all about avoiding shared mutable state.
I'm implementing a FRP engine, the par that evaluates the "pure, functional" abstraction.
Thank you for the detailed solution. This solves the problem I stated but not the problem I wanted to state. I need to figure out how to better phrase my requirements.
You could return a tuple with both the inner and the outer ref, so the outer one doesn't die too early, which is your problem.
alice
January 2, 2020, 7:49pm
12
Unfortunately such a tuple is not possible because one would be a borrow of the other, making it a self-referential type.
For what is worth, the following works:
use ::core::cell::{RefCell, RefMut};
fn foo<T> (x: &'_ RefCell<T>) -> RefMut<T>
{
x.borrow_mut()
}
fn foo2<T> (x: &'_ RefCell<RefCell<T>>) -> RefMut<T>
{
RefMut::map(x.borrow_mut(), RefCell::get_mut)
}
1 Like
I'm not sure I can make it work using RefMut
(i.e. might be borrowing it twice). However, the fact that this works at all is intriguing.
1 Like
Hyeonu
January 3, 2020, 1:54am
15
Because the RefCell::get_mut
doesn't acquire any lock. It's a simple projection from unique reference to unique reference, which is guaranteed to be succeed and sound.
Phlopsi
January 3, 2020, 12:01pm
16
Ahh, right. The address of the pointer would change. Thanks for pointing that out. I was somehow thinking about both pointers being independent, which isn't the case.
system
Closed
April 2, 2020, 12:01pm
17
This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.