rust/src/doc/trpl/mutability.md
2015-09-09 18:36:11 +05:30

4.3 KiB
Raw Blame History

% Mutability

Mutability, the ability to change something, works a bit differently in Rust than in other languages. The first aspect of mutability is its non-default status:

let x = 5;
x = 6; // error!

We can introduce mutability with the mut keyword:

let mut x = 5;

x = 6; // no problem!

This is a mutable variable binding. When a binding is mutable, it means youre allowed to change what the binding points to. So in the above example, its not so much that the value at x is changing, but that the binding changed from one i32 to another.

If you want to change what the binding points to, youll need a mutable reference:

let mut x = 5;
let y = &mut x;

y is an immutable binding to a mutable reference, which means that you cant bind y to something else (y = &mut z), but you can mutate the thing thats bound to y (*y = 5). A subtle distinction.

Of course, if you need both:

let mut x = 5;
let mut y = &mut x;

Now y can be bound to another value, and the value its referencing can be changed.

Its important to note that mut is part of a pattern, so you can do things like this:

let (mut x, y) = (5, 6);

fn foo(mut x: i32) {
# }

Interior vs. Exterior Mutability

However, when we say something is immutable in Rust, that doesnt mean that its not able to be changed: we mean something has exterior mutability. Consider, for example, Arc<T>:

use std::sync::Arc;

let x = Arc::new(5);
let y = x.clone();

When we call clone(), the Arc<T> needs to update the reference count. Yet weve not used any muts here, x is an immutable binding, and we didnt take &mut 5 or anything. So what gives?

To understand this, we have to go back to the core of Rusts guiding philosophy, memory safety, and the mechanism by which Rust guarantees it, the ownership system, and more specifically, borrowing:

You may have one or the other of these two kinds of borrows, but not both at the same time:

  • one or more references (&T) to a resource,
  • exactly one mutable reference (&mut T).

So, thats the real definition of immutability: is this safe to have two pointers to? In Arc<T>s case, yes: the mutation is entirely contained inside the structure itself. Its not user facing. For this reason, it hands out &T with clone(). If it handed out &mut Ts, though, that would be a problem.

Other types, like the ones in the std::cell module, have the opposite: interior mutability. For example:

use std::cell::RefCell;

let x = RefCell::new(42);

let y = x.borrow_mut();

RefCell hands out &mut references to whats inside of it with the borrow_mut() method. Isnt that dangerous? What if we do:

use std::cell::RefCell;

let x = RefCell::new(42);

let y = x.borrow_mut();
let z = x.borrow_mut();
# (y, z);

This will in fact panic, at runtime. This is what RefCell does: it enforces Rusts borrowing rules at runtime, and panic!s if theyre violated. This allows us to get around another aspect of Rusts mutability rules. Lets talk about it first.

Field-level mutability

Mutability is a property of either a borrow (&mut) or a binding (let mut). This means that, for example, you cannot have a struct with some fields mutable and some immutable:

struct Point {
    x: i32,
    mut y: i32, // nope
}

The mutability of a struct is in its binding:

struct Point {
    x: i32,
    y: i32,
}

let mut a = Point { x: 5, y: 6 };

a.x = 10;

let b = Point { x: 5, y: 6};

b.x = 10; // error: cannot assign to immutable field `b.x`

However, by using Cell<T>, you can emulate field-level mutability:

use std::cell::Cell;

struct Point {
    x: i32,
    y: Cell<i32>,
}

let point = Point { x: 5, y: Cell::new(6) };

point.y.set(7);

println!("y: {:?}", point.y);

This will print y: Cell { value: 7 }. Weve successfully updated y.