Variables and types
let, mut, type inference, and Rust's scalar types — the building blocks every Rust program is made of.
- Declare variables with let and understand immutability by default
- Use mut when a variable needs to change
- Name the four scalar types and their common literal forms
- Explain what type inference does and when to write a type annotation explicitly
In the fundamentals track, variables are described as names bound to values. Rust takes that idea and adds one more dimension: by default, the binding is immutable. That isn't a restriction for its own sake — it's information the compiler uses to check your code, and it's the first step toward the ownership model you'll meet in lesson 5.
let and immutability
let x = 5;x is now bound to the integer 5. Try to change it:
let x = 5;
x = 6; // compiler error!error[E0384]: cannot assign twice to immutable variable `x`
--> src/main.rs:3:5
|
2 | let x = 5;
| -
| |
| first assignment to `x`
| help: consider making this binding mutable: `mut x`
3 | x = 6;
| ^^^^^ cannot assign twice to immutable variableThe compiler tells you exactly what to do: add mut.
mut — opting into change
let mut count = 0;
count = count + 1;
println!("count is {}", count); // count is 1mut is an explicit declaration of intent: "this value will change." Code without mut is easier to reason about because you know values stay put. In practice, many variables in Rust programs don't need mut at all.
Immutability is the default for a reason. When you read a function and see let x = compute(y), you know x never changes in that function unless it's declared mut. This is the same idea behind const in modern JavaScript — but in Rust it's the default, not an opt-in.
Scalar types
Rust is statically typed: every variable has a type that the compiler knows at compile time. Most of the time you don't have to write the type yourself — the compiler infers it from the value. But you need to know the types exist.
Integers — whole numbers. The most common is i32 (32-bit signed integer). The i means signed (can be negative); u means unsigned (never negative). The number is the bit width.
let age: i32 = 30;
let score: u64 = 1_000_000; // underscores are allowed for readabilityFloating-point — numbers with a decimal. f64 is the default; it has double precision.
let temperature: f64 = 98.6;Boolean — exactly true or false. Written as bool.
let is_valid: bool = true;Character — a single Unicode character, written with single quotes and stored as a 4-byte Unicode scalar value.
let letter: char = 'A';
let emoji: char = '🦀';Rust's char is not a byte. It's a full Unicode character — 4 bytes. If you're working with ASCII text byte-by-byte, you'll use u8, not char. This distinction matters when you get to strings.
Type inference
Most of the time you can omit the type annotation — the compiler uses type inference to figure it out from context:
let x = 5; // inferred as i32
let pi = 3.14159; // inferred as f64
let flag = true; // inferred as boolWhen inference isn't enough — for instance, when you're creating an empty collection and the compiler can't see what will go into it — write the type explicitly:
let numbers: Vec<i32> = Vec::new();The colon-then-type syntax is how every annotation works in Rust: name: Type.
Check your understanding
Knowledge check
- 1.What happens if you try to assign a new value to a let variable without mut?
- 2.Which declaration lets you change the value of x later?
- 3.In Rust, you must always write an explicit type annotation on every variable.
Do it yourself
Open src/main.rs in your hello-world project and replace the main body with:
fn main() {
let mut score = 0;
score = score + 10;
score = score + 5;
println!("Final score: {}", score);
let name = "Rustacean";
println!("Hello, {}!", name);
}Run cargo run. Then try removing the mut from score and observe the compiler error — read the full message before you fix it.
Where to go next
You know how to bind names to values and what the basic types are. Next: functions — packaging logic into named, reusable pieces, and the distinction between expressions and statements that makes Rust functions feel a little different from what you might expect.