Full title: [Advanced Rust] 1.10. References and Interior Mutability (Quick Recap) - References, Interior Mutability, Cell Type, and Related Operations
1.10.1. References
Through references, Rust allows values to be borrowed without giving up ownership.
A reference is a pointer with an additional contract attached. Rust has two kinds of references.
1. Shared References
Shared references, also called immutable references, are written in Rust as &T, where T stands for a type.
Their characteristic is that any number of references can exist at the same time, or within the same scope, pointing to the same value. Every shared reference implements the Copy trait.
The value behind a shared reference is immutable. The compiler is allowed to assume that the value pointed to by a shared reference does not change while that reference is alive.
For example: if the value behind a shared reference is read multiple times inside a function, the compiler is allowed to read it once and then reuse the read value.
2. Mutable References
The counterpart to immutable references is the mutable reference, written in Rust as &mut T.
A mutable reference is exclusive, which means that within one scope there can be only one mutable reference; there cannot be a second mutable reference or any number of shared references. Therefore, mutable references do not implement the Copy trait (shared references do).
The compiler assumes that no other thread accesses the type pointed to by a mutable reference, whether through a shared reference or another mutable reference.
1.10.2. Owning a Value vs. Owning a Mutable Reference to a Value
The owner is responsible for deleting the value — or dropping it — and aside from that, the two behave mostly the same.
Note: if you move the value behind a mutable reference, you must leave another value in its place. If you do not, the owner will think it still needs to drop the value, but there is actually nothing left to drop, which leads to undefined behavior or a compilation error.
Take a look at this example:
fn main() {
let mut s = String::from("Hello");
let r = &mut s;
let t = *r; // Try to move the value pointed to by `r`
println!("{}", r); // `r` becomes a dangling reference
}
Output:
error[E0507]: cannot move out of `*r` which is behind a mutable reference
--> src/main.rs:5:13
|
5 | let t = *r; // Try to move the value pointed to by `r`
| ^^ move occurs because `*r` has type `String`, which does not implement the `Copy` trait
|
help: consider removing the dereference here
|
5 - let t = *r; // Try to move the value pointed to by `r`
5 + let t = r; // Try to move the value pointed to by `r`
|
help: consider cloning the value if the performance cost is acceptable
|
5 - let t = *r; // Try to move the value pointed to by `r`
5 + let t = r.clone(); // Try to move the value pointed to by `r`
|
Let’s walk through the process:
-
ris a mutable reference tos, and the*roperation tries to move the value (Stringdoes not implementCopy, soswould lose its data) - Since
sstill exists, Rust expects to be able to drop its memory normally whensgoes out of scope - But
shas already been moved away, so Rust no longer knows how to drop it correctly, which triggers a compilation error
The correct approach:
fn main() {
let mut s = String::from("Hello");
let r = &mut s;
let t = std::mem::replace(r, String::new()); // Replace the original value with an empty string
println!("{}", t); // "Hello"
println!("{}", s); // ""
}
1.10.3. Interior Mutability
Some types provide interior mutability, which allows them to modify values through shared references.
These types usually rely on extra mechanisms — such as atomic CPU instructions — or on invariants to provide safe mutability without relying on the semantics of exclusive references.
Interior mutability falls into two categories:
Obtain a mutable reference through a shared reference:
Mutex,RefCell
These types provide a guarantee: if a value is exposed through a mutable reference, then only one mutable reference will exist at the same time, and no shared references will exist alongside it. This capability relies onUnsafeCell, the only correct way to modify a value through a shared reference.Replace a value through a shared reference:
std::sync::atomic,std::cell::Cell
These types do not provide a mutable reference to the internal value, but they do provide methods for in-place operations on the value — for example, replacing or reading it. For instance, you cannot get a direct reference to ausizeori32, but you can read and replace the value.
1.10.4. The Cell Type
Cell comes from the standard library and provides interior mutability through invariants.
- A
Cellcannot be shared across threads, because its internal value is not meant to be modified concurrently, even when mutation happens through a shared reference - It does not provide references to the value inside the
Cell(so the value can always be moved)
Methods provided by Cell:
- Replace the value as a whole, which is the so-called in-place operation
- Return a copy of the value, which is reading
1. set(value): Replace the Value
use std::cell::Cell;
fn main() {
let x = Cell::new(10); // Create a `Cell` that stores 10
x.set(20); // Replace the internal value
println!("Updated value: {}", x.get()); // Prints 20
}
-
set(value)replaces the value inside theCellwith a new value
2. get(): Return a Copy of the Value
use std::cell::Cell;
fn main() {
let x = Cell::new(5);
let y = x.get(); // Get a copy of the value inside `x`
println!("Value: {}", y); // Prints 5
}
-
get()does not return a reference to the internal value; it returns a copy of the value (for types that implement theCopytrait). - It works for
i32,bool, and other types that implement theCopytrait.
Top comments (0)