Mutability
In R, most objects are effectively immutable — when you modify one, R copies it first. This is called copy-on-write semantics. Rust takes a stricter approach, making variables immutable by default, and the compiler enforces this at compile time. To make a variable mutable, you must declare it with the mut keyword:
let mut x = 5;
x = 6; // works because x is mutableWithout mut, attempting to reassign a variable is a compile error. This helps catch bugs early by making data changes explicit. Note that when updating a mutable variable, you do not re-bind it with let — you simply assign the new value directly.
Mutability is especially useful for accumulating values across a loop. In the previous section, we saw that variables defined inside a loop cannot be accessed outside it. The solution is to declare a mutable variable in the outer scope and update it inside the loop:
src/main.rs
fn main() {
let numbers = vec![1, 2, 3];
let mut doubled = 0;
for n in numbers {
doubled = n * 2;
println!("{} doubled is {}", n, doubled);
}
// `doubled` is accessible here because it was declared outside the loop
println!("Last doubled: {}", doubled);
}1 doubled is 2
2 doubled is 4
3 doubled is 6
Last doubled: 6You can also use += to add and assign in one step — x += 10 is equivalent to x = x + 10. This makes computing running totals concise:
src/main.rs
fn main() {
let x = vec![1.0, 2.0, 3.0, 4.0];
let n = x.len() as f64;
let mut total = 0.0;
for xi in x {
total += xi;
}
println!("The mean is: {}", total / n);
}The mean is: 2.5Like scalar variables, vectors must also be declared mut to modify them after creation. You can create an empty mutable vector and let Rust infer the data type based on how you go on to use it:
src/main.rs
fn main() {
let mut names = Vec::new();
names.push("Alice");
names.push("Bob");
println!("{:?}", names);
}["Alice", "Bob"]Here, Vec::new() creates an empty vector, and .push() appends an element to the end. Because the element appended is a character string, the Rust compiler is able to infer the type of the names vector at compile time. With this example, we have also introduced another method, namely .push(), which is particularly useful, but there are, in fact, many ways to modify mutable vectors in Rust. Let us mention just three here.
Clear – Instead of adding elements, you can remove all elements at once with .clear():
src/main.rs
fn main() {
let mut nums = vec![1, 2, 3];
nums.clear();
println!("{:?}", nums);
}[]Sort – Vectors can be sorted in place with .sort(). Not all types support ordering, but numeric types and strings do:
src/main.rs
fn main() {
let mut x = vec![11, 3, 7, 10, 1];
println!("x before sorting: {x:?}");
x.sort();
println!("x after sorting: {x:?}");
}x before sorting: [11, 3, 7, 10, 1]
x after sorting: [1, 3, 7, 10, 11]Extend – To combine two vectors, use .extend(), which appends the contents of one vector onto another:
src/main.rs
fn main() {
let mut a = vec![1, 2];
let b = vec![3, 4];
a.extend(b);
println!("{:?}", a);
}[1, 2, 3, 4]Note that the vector being extended must be mut and that the second vector is moved into the first — it can no longer be used after the call. We will elaborate on that last point more in the section on Rust’s memory model, specifically the concept of ownership.