Adding or subtracting one
Adding 1 to a variable, or subtracting 1 from it, is one of the most frequent changes a program makes: counting items, stepping through positions, counting down. Many languages have special operators for it, written ++ and --. Rust has neither. You use the augmented assignments += 1 and -= 1 instead.
let mut number: i32 = 10;
number += 1; // same as number = number + 1
println!("{}", number); // 11
number -= 1; // same as number = number - 1
println!("{}", number); // 10
Two things to notice. First, the variable must be declared with let mut, because Rust variables are immutable by default and these statements change the variable. Second, the compiler recognizes the habit from other languages and tells you what to write. Compiling x++; produces the error "Rust has no postfix increment operator" with the suggestion to use += 1.
Post-increment and pre-increment
In languages that have ++, the operator exists in two forms that behave differently when their value is used:
- Post-increment (
x++) gives the value from before the change, then increments. - Pre-increment (
++x) increments first, then gives the value from after the change.
In Rust, number += 1 is a statement, not a value you can use. As an expression it evaluates to the unit value (), so writing let y = (number += 1); stores () in y, which is rarely what anyone wants. To get the old or the new value, you spell out the order yourself with two statements.
Post-increment: read first, then change
let mut number: i32 = 10;
let old_number = number;
number += 1;
println!("{}", old_number); // 10
println!("{}", number); // 11
Because i32 is a plain number type, old_number receives a copy of the value. The later change to number does not affect it.
Pre-increment: change first, then read
let mut number: i32 = 10;
number += 1;
let new_number = number;
println!("{}", new_number); // 11
println!("{}", number); // 11
Decrementing works the same way with -= 1. Starting from 10, number -= 1; followed by let new_number = number; leaves both variables at 9.
A block can also package "change, then read" into one expression. In let y = { x += 1; x }; the block runs the increment and then produces the final x as its value. With x starting at 5, both x and y become 6.
A trap: --x compiles
The expression --x is accepted by Rust, but it is not a decrement. It is a double negation: -(-x), which is just x again, and nothing changes. The compiler warns that the prefix -- could be misinterpreted as a decrement operator. ++x is not valid at all and gives the error "Rust has no prefix increment operator".
The exercise, step by step
The exercise tracks one variable through increments, decrements and the two reading patterns. The values below show what each step produces.
- Create the variable.
let mut number = 18;usesmutbecause every later step changes it. - Increment.
number += 1;makes it 19. - Simulate post-increment. Save the current value in
postwithlet post = number;, then increment withnumber += 1;. Nowpostis 19 andnumberis 20. - Simulate pre-increment. Increment first with
number += 1;, which makes it 21, then capture it withlet pre = number;. Nowpreis 21. - Decrement.
number -= 1;bringsnumberto 20.
let mut number = 18;
number += 1;
let post = number;
number += 1;
number += 1;
let pre = number;
number -= 1;
Two number += 1; lines sit side by side between post and pre. The first is the change that the post-increment pattern makes after saving. The second is the change that the pre-increment pattern makes before saving. Printing the three variables at the end gives 19 21 20. The type of number is inferred as i32, since nothing says otherwise.
Common mistakes
- Writing
number++or++number. Both are compile errors. Writenumber += 1. - Expecting
--numberto decrement. It compiles but does nothing, apart from the compiler's warning. - Forgetting
mut. Without it the compiler reports "cannot assign twice to immutable variable". - Saving the value in the wrong order. If
let post = number;comes after the increment, it holds the new value, which is the pre-increment pattern. - Ignoring the type's range. In an optimized build, which is how these lessons are compiled, incrementing an
i32past its maximum wraps around silently. In a debug build, the same overflow panics.
Key takeaways
- Rust has no
++or--. Use+= 1and-= 1on alet mutvariable. - The assignment itself produces no useful value, so "value before" and "value after" are written as two statements in the order you need.
- Saving the value before the change imitates post-increment, and saving it after imitates pre-increment.
