Variables & Basic Types

Course 1 · Ch 2
Variables & Basic Types
Immutable by default, explicit scalar types, and a genuinely different kind of "redeclaring a variable"

Chapter 1 named memory safety as Rust's whole reason for existing. This chapter's first rule is a direct expression of that philosophy: variables are immutable by default — the opposite of what Go, JavaScript, and Python all do.

Immutability by Default

let x = 5; x = 6; // compile error: cannot assign twice to immutable variable

let x = 5; creates an immutable binding — attempting to reassign it is a compile error, not something that fails at runtime or silently succeeds. Mutability has to be opted into explicitly:

let mut x = 5; x = 6; // fine — x was declared mutable

This is a deliberate design choice, not an arbitrary restriction: most variables, in practice, are never reassigned after being set — making immutability the default means the compiler can catch an accidental reassignment as a mistake, rather than a program silently doing something the programmer didn't intend.

Scalar Types

Rust's basic scalar types are explicit about size and signedness, unlike Go's simpler int/uint or JavaScript's single Number type:

let a: i32 = -10; // signed 32-bit integer let b: u32 = 10; // unsigned 32-bit integer (no negatives) let c: f64 = 3.14; // 64-bit floating point (the default for decimals) let d: bool = true; let e: char = 'R'; // a single Unicode scalar value, 4 bytes — closer to Go's rune than a 1-byte char

Compound Types

A tuple groups a fixed number of values of mixed types; an array holds a fixed number of values of the same type. Both have a size fixed at compile time — a dynamically growable list is Vec<T>, covered in Chapter 8.

let point: (i32, f64, char) = (3, 9.5, 'Z'); let x = point.0; // tuples are indexed with .0, .1, .2 let scores: [i32; 5] = [90, 85, 78, 92, 88]; let first = scores[0];

Type Inference & Explicit Annotations

Rust infers types in most cases — let x = 5; is inferred as i32 without an annotation, the same convenience Go's := provides. An explicit annotation is only needed when the inferred default isn't what's wanted, or when the type genuinely can't be inferred from context alone.

let x = 5; // inferred as i32 let y: u8 = 5; // explicitly u8 instead

Shadowing

Rust allows re-declaring a variable with the same name using let again — this creates a brand-new binding that shadows the previous one, and can even change its type entirely. This is genuinely different from Go, where redeclaring a name in the same scope with := is usually a compile error unless at least one new variable is being introduced.

let input = "42"; let input: i32 = input.parse().unwrap(); // shadowed — now a number, same name

The second let input doesn't mutate the original string variable — it creates a separate variable that happens to share the name, and the earlier &str version is no longer accessible from this point in the scope onward.

ConceptGoRust
Default mutabilityMutable by defaultImmutable by default (mut opts in)
Type inference:= infers the typelet infers the type
Redeclaring the same nameCompile error (with :=, unless a new var is added)Allowed — shadowing, can change type
Fixed-size listArray (rarely used directly)[T; N] — fixed-size, same type
Immutability is the same safety philosophy from Chapter 1
Defaulting to immutable bindings is a small, early instance of the same idea Chapter 3's ownership model takes much further: the compiler catches an entire category of mistakes (accidental mutation) before the program ever runs, rather than leaving it to be discovered at runtime or in production.
Shadowing is not mutation — and it doesn't survive the scope
Shadowing requires using let again each time — a plain reassignment (input = ... with no let) is mutation, and requires mut, an entirely different mechanism. Shadowing is also scope-limited: once a shadowed variable's block ends, the outer variable (if one exists) becomes visible again, unaffected by whatever the inner shadow did. Confusing shadowing with mutation is a common early mix-up — they look superficially similar but follow completely different rules.

Coding Challenges

Challenge 1

Declare an immutable variable holding your age as a u8, then write a second line that attempts to reassign it. Run cargo run, note the exact compiler error, then fix it using mut.

📄 View solution
Challenge 2

Create a tuple holding a product's id (i32), price (f64), and category initial (char). Print each field individually using tuple indexing (.0, .1, .2).

📄 View solution
Challenge 3

Using shadowing, take a variable holding the string "100", and shadow it twice: first into an i32, then into that number multiplied by 2. Print the final value, and explain why this required let each time rather than plain reassignment.

📄 View solution

Chapter 2 Quick Reference

  • let x = ... — immutable by default; reassigning without mut is a compile error
  • let mut x = ... — explicitly opts into mutability
  • Scalar types: i32/u32/i64/u64 (explicit width + signedness), f32/f64, bool, char (4-byte Unicode scalar)
  • Tuples — fixed size, mixed types, indexed with .0/.1/.2
  • Arrays — fixed size, same type, [T; N]; a growable list is Vec<T> (Ch.8)
  • Shadowing — re-declaring with let creates a new binding, can change type, scope-limited
  • Shadowing ≠ mutation — shadowing needs let each time; mutation needs mut, no new let
  • Next chapter: Ownership — move semantics, one owner at a time, and why this eliminates the need for a garbage collector