Mutability
Mutability is an effect that allows modifying a value, which is enforced by Rusts type system and guarantees the absence of data races. The name "mutability" is problematic, because types with interior mutability can be changed even when the value is immutable.
Mutability of owned values
By default, owned values are immutable, which means that they can't be modified:
// x is immutable:
let x = 5;
x = 6;
// y is mutable:
let mut y = 5;
y = 6;
y += 1;
Only mutable variables, declared with the mut
keyword, can be reassigned. To make an immutable variable mutable, it must be moved to a new variable:
let x = 5;
let mut y = x;
y *= 2;
It is possible to shadow a variable name to achieve something similar to mutability:
let x = 5;
let x = x * 2;
Note that this declares two distinct variables which just happen to have the same name. With shadowing, it is not possible to change the value of an immutable variable. The difference is demonstrated by the below example, which shows how shadowing interacts with block scope:
let x = 1;
if true {
let x = 2;
}
assert_eq!(x, 1); // x was not changed, just shadowed
let mut y = 1;
if true {
y = 2;
}
assert_eq!(y, 2); // y was actually mutated
Function parameters and other bindings
let
bindings are patterns, as are function parameters. The mut
keyword is allowed in all patterns where a new binding is introduced, for example:
// destructure a tuple:
let (mut x, mut y) = (1, 2);
// match on an array:
let array = [1, 2, 3];
match array {
[mut x, _, _] => {}
}
// bind a parameter mutably:
fn foo(mut a: i32) {}
Note that a variable doesn't need to be mutable in order to pass it to a function or match
block where it is bound mutably, because the variable is moved in that case.
Mutability of borrowed values
When borrowing a value, it can be borrowed either mutably or immutably. This is done by creating a mutable or immutable reference:
let x = 42;
// create three references to x:
let ref1 = &x;
let ref2 = &x;
let ref3 = &*ref1;
let mut y = 42;
// create a mutable reference to y:
let mut_ref = &mut y;
While you can create as many immutable reference to a value as you want, mutable references must be exclusive: At any point, there can be at most one mutable reference to a value. Furthermore, while a mutable reference exists, no immutable reference may exist. This ensures that a variable can't be accessed while it is being modified by someone else. Example:
fn foo(mut x: i32) -> i32 {
let ref1 = &mut x;
*ref1 += 3;
// a mutable reference already exists
// but that's okay, since it isn't used after line 3
let ref2 = &x;
x += 4; // ERROR!
*ref2
}
This doesn't work because the value of x
is mutably borrowed twice, by both x
and ref2
.
The term "mutability" is problematic, because a value behind an immutable reference can be mutated, if its type has interior mutability (see below). Therefore some people recommend to call immutable references "shared references" instead; mutable references should be called "exclusive references". This is less ambiguous and more in line with Rusts memory model: When a reference is exclusive, it has exclusive access to the data it refers to, even in the presence of interior mutability.
Mutating functions
It's possible to mutate a value in a function through a mutable reference, for example:
#[derive(Debug)]
struct Point(i32, i32);
impl Point {
fn inv(&mut self) {
self.0 = -self.0;
self.1 = -self.1;
}
}
let mut p = Point(1, 4);
p.inv();
println!("{:?}", p); // prints Point(-1, -4)
Note that p.inv()
is equivalent to Point::inv(&mut p)
. If p
isn't mutable, it can't be mutably borrowed, so its inv()
method can't be called.
Interior mutability
See also: Reference – Interior mutability
It is possible to create types that can be mutated even if they're behind an immutable reference. This is done with the UnsafeCell
type. As the name already suggests, its usage is unsafe. However, it is possible to create safe abstractions on top of it. For example, the standard library contains the Cell
, RefCell
, Mutex
and RwLock
types, as well as atomic
types. These use UnsafeCell
internally, but perform runtime checks to ensure the absence of data races. This is only necessary if Rust is unable to guarantee the absence of data races at compile time.
Cell
The Cell
type is the simplest type that allows interior mutability; it allows moving an new value into the Cell
. However, it doesn't allow taking a reference to the inner value, and isn't thread safe.
let cell = Cell::new(5);
cell.set(6);
cell.set(cell.get() * 2);
RefCell
The RefCell
type is a more powerful version of Cell
, since it allows obtaining a mutable reference to the inner value, or multiple immutable references. Like Cell
, it isn't thread safe.
let ref_cell = RefCell::new(String::from("Hello, world"));
{
let mut s: RefMut<String> = ref_cell.borrow_mut();
s.push('!');
}
println!("{:?}", ref_cell);
RefCell
uses the guard pattern to check when the inner value is borrowed: The borrow_mut()
method checks if the RefCell
is already borrowed mutably and panics if that is the case. Then it increments a counter and returns a RefMut
guard that acts as a mutable reference. When this guard goes out of scope, the counter is decremented again.
Mutex
A thread-safe version of RefCell
. Note that the inner value can't be borrowed immutably multiple times. To do that, use RwLock
. Bear in mind that locking a Mutex
or RwLock
can deadlock.
let mutex = Mutex::new(String::from("Hello, world"));
{
let mut s: MutexGuard<String> = mutex.lock().unwrap();
s.push('!');
}
println!("{:?}", mutex);
Like RefCell
, it uses the guard pattern. Note that lock()
returns a Result
, in case the mutex was poisoned. It's usually okay to just unwrap the result.
RwLock
A read-write-lock. It is the most powerful, thread-safe type for interior mutability in the standard library. It can be mutably borrowed once or immutably borrowed multiple times.let rw_lock = RwLock::new(String::from("Hello, world"));
{
let mut s: RwLockWriteGuard<String> = rw_lock.write().unwrap();
s.push('!');
}
println!("{:?}", rw_lock);
Mutex
, except that it has the read()
and write()
methods instead of lock()
.
Atomic*
types
The standard library contains several wrappers around primitive types, such as AtomicBool
, AtomicI32
, AtomicUsize
, etc. These allow accessing and mutating values shared across multiple threads. The underlying value can only be modified via their methods, which are atomic. There are five different strategies how these types can synchronize memory; these are enumerated in the Ordering
struct.let b = AtomicBool::new(true);
assert_eq!(true, b.load(Ordering::Relaxed));
b.store(false, Ordering::Release);
assert_eq!(false, b.load(Ordering::Acquire));
Summary
Type | allows borrowing the inner value |
allows multiple immutable borrows |
thread safe |
---|---|---|---|
Cell
|
no | no | no |
RefCell
|
yes | yes | no |
Atomic* types
|
no | no | yes |
Mutex
|
yes | no | yes |
RwLock
|
yes | yes | yes |
Mutable static variables
Main article: Static variables
Static variables can be mutable. Since Rust can't prove the absence of data races of global mutable variables, accessing a static mutable variable is unsafe.