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
}

Enter fullscreen mode Exit fullscreen mode

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`
  |

Enter fullscreen mode Exit fullscreen mode

Let’s walk through the process:

  • r is a mutable reference to s, and the *r operation tries to move the value (String does not implement Copy, so s would lose its data)
  • Since s still exists, Rust expects to be able to drop its memory normally when s goes out of scope
  • But s has 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);  // ""
}

Enter fullscreen mode Exit fullscreen mode

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 on UnsafeCell, 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 a usize or i32, 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 Cell cannot 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
}

Enter fullscreen mode Exit fullscreen mode

  • set(value) replaces the value inside the Cell with 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
}

Enter fullscreen mode Exit fullscreen mode

  • get() does not return a reference to the internal value; it returns a copy of the value (for types that implement the Copy trait).
  • It works for i32, bool, and other types that implement the Copy trait.