Skip to content
Variables and Mutability
step 1/5

Reading — step 1 of 5

Learn

~3 min readGetting Started

Variables and Mutability

Here is Rust's first genuinely contrarian decision: variables don't vary unless you ask.

let x = 5;
x = 6;          // ERROR: cannot assign twice to immutable variable `x`

let mut y = 5;  // `mut` = "I intend to change this"
y = 6;          // fine

Every other mainstream language defaults to mutable and offers const as an opt-in. Rust flips it: immutable by default, mut as a declaration of intent. The payoff compounds: when you read someone's code (including last-month-you's), every variable that can change is marked at its birthplace. Everything else is a fixed fact. Whole categories of "who changed this?!" debugging simply don't exist.

The compiler enforces it both ways — it errors if you mutate a non-mut, and warns if you mark something mut and never change it. The annotations stay honest.

Types: inferred, but real

Rust is statically typed with strong inference:

let count = 42;          // i32 — the default integer
let price = 9.99;        // f64 — the default float
let active = true;       // bool
let letter = 'A';        // char (single quotes; a full Unicode code point)

let size: u64 = 1024;    // annotate when you want a specific type

The integer menu is explicit about width and signedness: i8/i16/i32/i64/i128 signed, u8u128 unsigned, usize for indexing/lengths. Defaults (i32, f64) are right until a protocol or index says otherwise. And as in Go — no implicit conversions, ever: i32 + f64 is a compile error until you write as f64 (or better, from/into). Where numbers change type, the code says so.

Integer overflow gets Rust's signature treatment: in debug builds it panics (crashes with a message) instead of silently wrapping. The bug you'd chase for a day elsewhere announces itself on first run.

Shadowing: the idiom that surprises

Rust lets you let the same name again — and it's not reassignment, it's a new variable that shadows the old one:

let input = "42";                       // &str
let input: i32 = input.parse().unwrap(); // now an i32 — same name, new variable

This is idiomatic and useful: transformation pipelines keep one honest name (input) instead of input_str, input_parsed, input_final. Note what shadowing can do that mut can't — change the type — and that it works without mut because nothing is mutated; the old variable simply stops being visible.

Constants

const MAX_RETRIES: u32 = 3;

const requires the type annotation, a compile-time value, and SCREAMING_SNAKE_CASE by convention. Use it for genuine program-wide constants.

Your exercise: Sum Two Numbers

Read two integers from stdin, print their sum. Reading input in Rust is more ceremony than you're used to — here's the pattern, worth keeping on a mental index card:

use std::io::Read;

fn main() {
    let mut input = String::new();
    std::io::stdin().read_to_string(&mut input).unwrap();
    let mut it = input.split_whitespace();
    let a: i64 = it.next().unwrap().parse().unwrap();
    let b: i64 = it.next().unwrap().parse().unwrap();
    println!("{}", a + b);
}

read_to_string slurps all of stdin, and split_whitespace doesn't care whether the numbers were separated by spaces or newlines — this exact pattern works for every input layout the graders use, so it's worth memorizing as-is. Notice who needs mut (the input buffer and the iterator — both get modified) and who doesn't (a, b — read, never changed). The unwrap()s say "crash if this fails" — a placeholder you'll replace with real error handling in chapter 6, and perfectly acceptable for grader input that's promised to be well-formed.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…