This article refers to rust book ch15 and adds its own understanding, interested parties can first look at the official documentation.
Rust has two ways to achieve mutability
- Inheritance variability: for example, if a struct is declared with let mut, then any field of the struct can be modified later.
- Internal mutability: use
Cell
RefCell
to wrap a variable or field so that it can be modified even if the external variable is read-only
It seems that inheritance mutability is enough, so why do we need the so-called interior mutability
internal mutability? Let’s analyze two examples.
|
|
The structure Cache
has three fields, x
, y
, sum
, where sum
simulates the lazy init lazy loading mode, the above code is not working, the reason is simple, when let initialize the variable i, is immutable.
There are two ways to fix this problem, the let declaration specifies let mut i
, but for larger projects the outer variables are likely to be immutable and immutable. This is where internal mutability comes in handy.
Fix
|
|
This is the code after the fix, sum type is Cell<Option<i32>>
, beginners are easily confused, what the hell is this ah? Some also nest multiple wrappers, like Rc<Cell<Option<i32>>
and so on.
For example, Rc
represents shared ownership, but because the T in Rc<T>
is read-only and cannot be modified, we need to use Cell
to seal a layer so that ownership is shared, but still mutable, and Option
is the common value Some(T)
or the null value None
, which is still very understandable.
If you are not writing rust code and just want to read the source code to understand the process, there is no need to delve into these wrappers, focus on the real type of the wrapper can be.
The example given by the official website is Mock Objects, the code is longer, but the principle is the same.
Finally, it’s all about wrapping the structure fields with RefCell
.
Cell
This code is very representative, if the variable a
is not wrapped with Cell
, then it is not allowed to modify a
while b
read-only borrow exists, as guaranteed by the rust compiler during the compile period: Given a pair, only N immutable borrowings or one mutable borrowing are allowed to exist in scope (NLL).
Cell
gets and modifies values via get
/ set
, this function requires that value must implement the Copy
trait, if we replace it with another structure, the compile will report an error.
|
|
From the above, we can see that struct Test
does not implement Copy
by default, so get
is not allowed. Is there any way to get the underlying struct? You can use get_mut
to return a reference to the underlying data, but this requires the entire variable to be a let mut
, so it is not consistent with the original intent of using Cell
.
RefCell
Unlike Cell
, we use RefCell
to get either immutable borrowing via borrow
or mutable borrowing of the underlying data via borrow_mut
.
|
|
Code from badboi.dev, compiled successfully, but failed to run.
|
|
cell_ref_1
calls borrow_mut
to get a variable borrow, and while it is still in scope, cell_ref_2
also tries to get a variable borrow, at which point the runtime check reports an error and panic directly.
That means RefCell
moves the borrow rule from compile-time compile to runtime , with some runtime overhead.
|
|
This is the official example, used in combination with Rc
, RefCell
to share ownership while modifying List node values.
Summary
Internal mutability provides great flexibility, but filtered to runtime overhead, still can not be abused, performance problems are not significant, the focus is the lack of compile-time static checks, will cover up many errors.