Say we need a
Cursor<T> type, which should contain a mutable reference to
T. The type has a method
.dup() that will create a new instance of
Cursor<T> while sharing the reference between new and old instances. This is common in database driver designing, where users might expect to have multiple cursors alive simultaneously, owning references to the same connection object. A possible implementation might look like:
Perfect and neat, and luckily Rust compiler did not complain. As a Rust freshman, like me, it’s not easy to keep compiler silent all the time, especially when playing with references. But once I try to chain up the constructor and
.dup(), the compiler screams:
It’s confusing. In most languages such as Python, either splitting the calling or chaining them should have no difference, but Rust sometimes rejects the latter one.
To figure out the problem, let’s write the signature of
.dup() in a more verbose way:
Rust compiler allows us to elide the lifetime specifier
'a. The signature implies that
self should live at least as long as the returned object. But the chaining is in fact something like:
The temporary variable
__a lives only in the block, and is dropped at the end of it. But the result of
__a.dup() is passed out of the block, and thus it has a longer lifetime than
__a. This conflicts with the signature. To fix it, we might instead use a self-consuming version of
.into_dup() moves all content in
self into the new object, and makes no implication on the lifetime. Now we can write code like
However, the solution above is not satisfactory enough. Apparently we don’t want to make a self-consuming version for each method, just in order to please the compiler. In fact, a smart pointer with reference counting may be more suitable for the job. So let’s rewrite it using
Rc instance could be regarded as a wrapper for a pointer (or reference), and cloning the instance implies duplicating the pointer. But from the perspective of compiler, there’s no borrowing involved, and no more need for explicit lifetime declarations in
impl. We can now use method chaining as expected:
Great! Looks like that
Rc is an helper which relaxes the constraints of references, and allow us to do more flexible reference sharing.
Now let’s try something else — mutating the target through a reference:
We can read the inner value by directly dereferencing an
Rc<T> instance, as
Rc implements the
Deref trait. But when we want to mutably dereference it, we need to use
Rc::get_mut. The function returns an
Option<&mut T> instead of simply
&mut T. This may be wierd at the first sight, since
Rc objects own the target, and you can come up with no occasion that the reference might become invalid. Let’s just ignore it now and compile the code. The compiler complains nothing. But when we run the code, it panics:
The fact is that
Rc::get_mut indeed returns a
None! For some reason a mutable reference is not available at that time. Let’s turn to the docs for help:
Rc::get_mut) Returns a mutable reference to the inner value, if there are no other
Weakpointers to the same value.(
Noneotherwise, because it is not safe to mutate a shared value.
Rc::get_mut will ensure there’s no other
Weak instances referencing the target, and fails (returns
None) otherwise. In the code above, we actually has two
a.obj, referencing the inner value, so
Rc::get_mut denies the borrow. Seems like it’s just another borrow checker! Except that
Rc do the checking at runtime. We may make a quick comparison between
Rcs and references:
- At any moment there may exists several
Rc<T>instances referencing the same inner value. One can use
*to directly deference one of them to immutably access the inner value.
Rc<T>instance can be mutably dereferenced if and only if it is the only instance referencing the target.
- For a value
T, at any moment there may exists several immutable reference to
v. Each can be derefenced and returns the pointed value.
vcan exist, if and only if there is no other reference to
Yeah, Rust is safe… But is our design wrong? Not really. We may just misuse the underlying structure.
The problem is, the existence of an
Rc<T> instance implies an “immutable reference” to the inner value, which blocks out mutable access to the target. However, this is not exactly what we want. Usually we hold a reference to an object because it may be (mutably or immutably) referenced in the future. The read/write actions may just occupy a very short period in the whole lifetime of program. It would be better if the checking is done at the time the inner value is going to be read.
But we still need
Rc, since it’s almost the only choice for reference sharing. The trick is that we should find some ways to mutate the inner value, while only given an immutable reference.
What makes Rust annoying is that, the concept mutability is “infectious”. If one want to modify a part of an object, he must have a mutable reference to the whole object. It’s inconvenient for objects which consist of several independent parts. Such design is not always bad, but it does not play well with the borrow checker. For more generic designs, sometimes mutability of an object should be hidden, so that we can mutate the object with only immutable reference given. This is what we called Interior Mutability Pattern in Rust.
RefCell is designed to compete the job. A
RefCell instance has
.borrow_mut() method for corresponding borrowing. Both the two methods have parameter
&self, so there’s no requirement for the instance’s compile-time mutability. However, it will still panic if the borrowing break the rules, but for most of the time we can use it with no bothers.
Rc<RefCell<T>> to rewrite our code:
The declaration may look a bit verbose, but, it did work! We can now share the reference between objects, and as well mutate the inner value through a mutable borrowing. Note that because of the automatic dereference behavior in Rust, the
Rc layer is in fact transparent and we can write code like
i.borrow_mut(). This saves us from becoming overly verbose.
Now if we add an additional line:
The program still compiles, but will panic at runtime:
Since at the moment of
i.borrow_mut(), there already exists another immutable borrowing
_ref, which break the rules. The example shows that
RefCell still do the checking at runtime, so it’s safe enough to use.