Immutability by default

I had a bit of trouble with that term in the beginning. Immutable basically means non-changeable, in other words, read-only. It's not precisely the same, but it's close enough for understanding. The general advantage of having read-only values is pretty clear: You can trust a block of code to not change a variable. From the other side, you can't randomly change values that shouldn't.

In my eyes, making this behaviour the default is a pretty smart thing, because programmers rarely put in the work to declare something read-only, even if it is. On the other hand, making something mutable explicitly tells you to expect the value to change.

For this, Rust uses the mut keyword in front of the identifier. If you use it without mutating the value down the road, the compile will issue a warning. For immutable variables, Rust is extremely flexible with when to assign it and can even check multiple paths for exactly one assignment.


#![allow(unused)]
fn main() {
let answer;

answer = 42; // works
answer = 69; // doesn't work

let fun;
let having_fun = true;

// MUST have an else block, otherwise not all paths
// would provide exactly one assignment
if having_fun {
  fun = 69;
} else {
  fun = 0;
}
}

You may ask yourself: What do I need immutable variables for? What can I even do with them? Apart from actually writing algorithms (and not even always there), you'll find that you barely need mutable variables. In many cases, you either only need them as a function parameter, or you change the context by assigning the value to a new variable with a better fitting name.

Now that is all well and good, but the really interesting part starts with objects and functions. The mutability rule also applies to function parameters. If a function wants a mutable reference as a parameter, it's an explicit sign for you that the function will change the element.

// 'normal' function
fn add_two_value(start: usize) -> usize {
  start + 2
}

// function with a mutable reference
fn add_two_reference(value: &mut usize) {
  *value += 2;
}

fn example() {
  let immutable = 42;
  let mut mutable = 69;
  
  let value_result = add_two_value(immutable);
  add_two_reference(&mut mutable);
  
  println!("{}", value_result);
  println!("{}", mutable);
}
fn main() { example(); }

For immutable objects, you can only call methods that don't need the object to be mutable. For example, getters work on them, while setters don't. Although mutability is part of the variable type, you can use mutable types in place for immutable types, too (but obviously not the other way around). You can use this to put mutable variables in an immutable context.


#![allow(unused)]
fn main() {
// immutable reference
fn extract_len(my_vec: &Vec<usize>) -> usize {
  my_vec.push(42); // doesn't work
  my_vec.len()     // works
}

fn example() {
	let mut my_vec = vec![0, 1, 2, 3];
	// immutable context
	let len = extract_len(&my_vec);
}
}

There is one workaround for these rules that I see a bit critically. Rust supports shadowing, meaning defining a new variable with the same name as an existing one. This is no problem if you do it inside a new code block, but in the same block, you can actually use it to work around the immutability rule and change the value found under the identifier.


#![allow(unused)]
fn main() {
let having_fun = true;
let answer = 1;
let fun = 69;

// This can trip you up
let answer = 42;

if having_fun {
  // Has no effect outside of this block
  let fun = 9001;
}

println!("{}", answer);
println!("{}", fun);
}