Step 1 of 5 · Reading · ~3 min
Learn
Getting Started
Variables and Mutability
By the end of this lesson you can declare variables, say which ones are allowed to change with mut, update them with +=, and turn a line of text into a number by shadowing its name.
let bindings are immutable by default
let creates a binding: a name attached to a value. Unless you say otherwise, the binding can never be given a new value.
let steps = 100;
steps = 350; // error[E0384]: cannot assign twice to immutable variable `steps`
Add mut ("mutable") when a value is meant to change. += adds to the current value in place, so steps += 250 means steps = steps + 250.
let mut steps = 100;
steps += 250;
println!("{}", steps); // 350
steps += 50;
println!("{}", steps); // 400
Each println! shows the value at that moment, so the same name prints two different numbers. The compiler checks both directions: assigning to a non-mut binding is an error, and marking something mut that never changes earns a warning.
Types: inferred, but fixed
Every binding has one type, chosen once. Rust infers it from the value, or you write it after a colon:
let lives = 3; // i32, the default integer
let speed = 2.5; // f64, the default float
let done = false; // bool
let big: i64 = 5000000000;
An i32 holds up to 2,147,483,647. Anything larger needs i64 (up to about 9.2 quintillion). There are no silent conversions between number types; the next lesson covers arithmetic and casts in detail.
Shadowing: same name, new variable
Writing let again with an existing name creates a new variable that hides (shadows) the old one. Unlike mut, shadowing may change the type:
let reading = " 21 "; // &str, with spaces around it
let reading = reading.trim(); // &str: "21"
let reading: i32 = reading.parse().unwrap();
println!("{}", reading + 1); // 22
trim() removes whitespace from both ends. parse() turns text into a number; the : i32 annotation tells it which number type to produce, and unwrap() means "crash if this failed". Without the annotation the compiler stops with type annotations needed.
The trap is skipping trim(). " 21 ".parse::<i32>() fails because spaces are not digits, and unwrap() turns that into a runtime panic: called Result::unwrap() on an Err value: ParseIntError. The same panic happens if the text holds a number too big for the type, such as "3000000000" parsed as i32.
Reading lines
The starter reads input like this. lines() gives one String per input line; the first unwrap() says "there is a line", the second says "it was read without error":
let stdin = io::stdin();
let mut lines = stdin.lock().lines();
let word = lines.next().unwrap().unwrap();
lines is mut because each next() moves it forward.
Constants
const MAX_LIVES: u32 = 3;
A const must have a written type and a value known at compile time, and is named in SCREAMING_SNAKE_CASE. It can never be mut.
Common mistakes
- Parsing without
trim(). An input line like42panics with aParseIntErrorinstead of printing a number. - Using
i32. An input of3000000000does not fit; the parse panics. Usei64. - Printing the second number instead of the total. For
7then5, the second line must be12, not5: add to the running total before printing it. - Forgetting
mut. Adding to a total declared withoutmutis compile error E0384.
Your exercise
Keep a running total of two numbers and print it after each one. The starter already reads both lines as text into first and second; you shadow each into an i64, keep a mut total, and print it twice. The most likely mistake is parsing without trim(), which crashes on lines with spaces.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…