Rc, RefCell and Interior Mutability
Say we need a type Cursor<T>
, which holds a mutable reference to T
. A method .dup()
duplicates the internal reference, wraps it in a new instance of Cursor<T>
and returns. Such pattern exists commonly in database driver library. Users could hold multiple cursors simultaneously, with each owning a (mutable) reference to the same connection object.
One might implements with a primitive mutable reference:
struct Cursor<'a, T> {
obj: &'a mut T,
}
impl<'a, T> Cursor<'a, T> {
fn new(t: &'a mut T) -> Cursor<'a, T> {
Cursor { obj: t }
}
fn dup(&mut self) -> Cursor<T> {
Cursor { obj: self.obj }
}
}
fn main() {
let mut i = 1;
let mut cursor_a = Cursor::new(&mut i);
let _cursor_b = cursor_a.dup();
}
Perfect and neat, and luckily Rust compiler did not complain. Fresh Rustanceans would have to work hard for shutting up the compiler, especially when fighting with references.
The invocation of ::new()
and .dup()
are on separate lines. Now what about to chain up the constructor and .dup()
? This time the compiler fails:
fn main() {
let mut i = 1;
let a = Cursor::new(&mut i).dup();
println!("{:?}", a.obj);
}
|
21 | let a = Cursor::new(&mut i).dup();
| ^^^^^^^^^^^^^^^^^^^ - temporary value is freed at the end of this statement
| |
| creates a temporary which is freed while still in use
22 | println!("{:?}", a.obj);
| ----- borrow later used here
|
= note: consider using a `let` binding to create a longer lived value
Wierd. Chaining invocations or not should make no difference, at least in most languages – but not in Rust.
To figure out the problem, let’s expand the signature of .dup()
into a more verbose one:
fn dup<'a>(&'a mut self) -> Cursor<'a, T> {
// ...
}
Rust compiler allows us to elide the lifetime specifier 'a
. The signature implies self
should live at least as long as the returned value. However, by chaining the invocations, the code is in fact equivalent to:
let a = {
let mut __a = Cursor::new(&mut i);
__a.dup()
};
The temporary variable __a
lives only within the block, and will be dropped at the end of it. But the return value of __a.dup()
is passed out of the block, and therefore has a longer lifetime than __a
, which violates the constraints. To fix it, we might instead implement a self-consuming version of .dup()
, say .into_dup()
:
fn into_dup(self) -> Cursor<'a, T> {
Cursor { obj: self.obj }
}
.into_dup()
moves all content of self
into the new object, and makes no implication on the lifetime. Now we can write code like Cursor::new(&mut i).into_dup()
.
Rc
The solution above is not satisfactory enough. Apparently we won’t make a self-consuming version for each method, just to please the compiler. A smart pointer with reference counting might be more suitable for the job. Let’s try to rewrite the code with std::rc::Rc
:
use std::rc::Rc;
struct Cursor<T> {
obj: Rc<T>,
}
impl<T> Cursor<T> {
fn new(t: &Rc<T>) -> Cursor<T> {
Cursor { obj: t.clone() }
}
fn dup(&self) -> Cursor<T> {
Cursor {
obj: self.obj.clone(),
}
}
}
An Rc
instance wraps a pointer (or reference) internally, and cloning the instance implicitly duplicates the pointer. But from the perspective of compiler, there’s no borrowing involved, and no more need for explicit lifetime declaration in struct
and impl
. We can now chain the invocations as expected:
fn main() {
let mut i = Rc::new(1);
let a = Cursor::new(&i).dup();
println!("i: {:?} a.obj: {:?}", a.obj, i);
}
// Output
// i: 1 a.obj: 1
Great! Looks like that Rc
could help to relax the constraints of references, and allow us to share references more flexibly.
Now let’s move forward to another task – we attempt to mutate the target value through the reference:
fn main() {
let mut i = Rc::new(1);
let a = Cursor::new(&i).dup();
// hl:begin
*Rc::get_mut(&mut i).unwrap() = 2;
// hl:end
println!("i: {:?} a.obj: {:?}", a.obj, i);
}
The highlighted
line is much more verbose than expected. Rc
does not implements DerefMut
trait, so we could not obtain a mutable reference by mutable de-referencing. Instead, it provides Rc::get_mut()
for the obj, which returns a value of type Optional<&mut T>
. The Optional
wrapper implies a possible failure during de-referencing. This could be confusing at the first sight – the object of type T
is owned by the Rc<T>
object, so how could the de-referencing be invalid?
We might defer the question in later paragraphs and go ahead. The code compiles smoothly, but this time we get a runtime panic:
thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', src/libcore/option.rs:347:21
Oops… So Rc::get_mut
does return a None
! To explain this, we might go back to the docs for help:
The fact is that, Rc::get_mut
help survive the compilation, but will perform a runtime check to ensure there’s no other Rc
or Weak
instances referencing the target, and it fails (returns None
) if so. This is claimed in the docs
Rc::get_mut
Returns a mutable reference to the inner value, if there are no otherRc
orWeak
pointers to the same value.Rc::get_mut
ReturnsNone
otherwise, because it is not safe to mutate a shared value.
In the code above, we actually have two Rc
instances, i
and a.obj
, sharing a same value internally. Rc::get_mut
therefore denies the dereferencing. From this perspective, Rc
resembles another borrow checker at runtime.
Here’s a brief comparison between Rc
s and primitive references:
Rc
:
- At any moment there may exist several
Rc<T>
instances holding reference to the same internal value. One can use*
to directly deference one of them to immutably access the internal value. - An
Rc<T>
instance can be mutably dereferenced if and only if it is the only instance holding reference to the internal value.
Primitive references:
- For a value
v
of typeT
, at any moment there may exist several immutable reference tov
. Each of them can be derefenced to obtain the target value. - An
&mut T
tov
is legal, if and only if there is no other reference tov
.
Sure, Rust is safe… But is our design incorrect? Not really. We may just misuse the underlying structure for the pattern.
The problem is, existence of an Rc<T>
instance implies an “immutable reference” to the internal value, and thus disallows mutable access to it in the future. The implication drives out data races, but could be too aggressive for our scenario. In some cases, accessing and mutating an object will never collide. It would be better weaken the notion of “holding reference” from the long living time of Rc<T>
to a much shorter one, for example, we could defer the checking to the time inner value being accessed, instead of the time Rc<T>
being duplicated.
RefCell and Interior Mutability Pattern
But we would still need Rc
, since it’s almost the only choice for reference sharing. The trick is to find some ways to mutate the inner value with only an immutable reference provided.
In Rust, the concept mutability is “infectious”. Even if we just attempt to partially mutate an object, a mutable reference to the whole object is still needed. It’s inconvenient for objects consisting of several independent parts 1. Such design does not play well with the borrow checker. From a more generic view, sometimes mutability of an object should be hidden, in order that it can be mutated with only immutable reference provided. This is what we called Interior Mutability Pattern in Rust.
The corresponding underlying structure is RefCell
. A RefCell
instance has .borrow()
and .borrow_mut()
method for immutable or mutable borrowing. Both of the two methods have &self
in signature, and thus no requirement for compile-time mutability. For most of the time, they can be used as expected without any disturbing, but at the time the borrowing violate the rules, they would still panic.
Rc + RefCell
We could combine Rc
and RefCell
into Rc<RefCell<T>>
and rewrite our program into expected behavior:
use std::cell::RefCell;
use std::rc::Rc;
struct Cursor<T> {
obj: Rc<RefCell<T>>,
}
impl<T> Cursor<T> {
fn new(t: &Rc<RefCell<T>>) -> Cursor<T> {
Cursor { obj: t.clone() }
}
fn dup(&self) -> Cursor<T> {
Cursor {
obj: self.obj.clone(),
}
}
}
fn main() {
let i = Rc::new(RefCell::new(1));
let a = Cursor::new(&i).dup();
*i.borrow_mut() = 2;
println!("i: {:?} a.obj: {:?}", a.obj, i);
}
The declaration is getting a little more verbose, but it does work! We can now share the reference between objects, and mutate the interval value via an immutable borrowing. With automatic dereference in Rust, the Rc
layer becomes transparent. We can write statement like i.borrow_mut()
to reduces most of the verbosity.
Now let’s see what would happen if multiple borrowings exist:
fn main() {
let i = Rc::new(RefCell::new(1));
let a = Cursor::new(&i).dup();
// hl:begin
let _ref = i.borrow(); // Another borrow here
// hl:end
*i.borrow_mut() = 2;
println!("i: {:?} a.obj: {:?}", a.obj, i);
}
The program still compiles, but panicks at runtime:
thread 'main' panicked at 'already borrowed: BorrowMutError', src/libcore/result.rs:999:5
At the moment of i.borrow_mut()
, another immutable borrowing _ref
already exists and violates the rules. In a sense RefCell
relaxes compile-time constraints and defers them to runtime. It makes a trade-off between compile-time safety and flexibility.
References
- One might argue this as an anti-pattern, as one class (type) should only do a job. But IMO languages should constraint less on high-level design patterns. It’s okay to explore the feasibility in Rust.
Author: hsfzxjy.
Link: .
License: CC BY-NC-ND 4.0.
All rights reserved by the author.
Commercial use of this post in any form is NOT permitted.
Non-commercial use of this post should be attributed with this block of text.
OOPS!
A comment box should be right here...But it was gone due to network issues :-(If you want to leave comments, make sure you have access to disqus.com.