Code of the Day
BeginnerCore syntax

Variables and types

let, mut, type inference, and Rust's scalar types — the building blocks every Rust program is made of.

RustBeginner10 min read
Recommended first
By the end of this lesson you will be able to:
  • 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 . 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 variable

The 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 1

mut 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 : every variable has a type that the compiler knows at . 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 readability

Floating-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 to figure it out from context:

let x = 5;          // inferred as i32
let pi = 3.14159;   // inferred as f64
let flag = true;    // inferred as bool

When 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. 1.
    What happens if you try to assign a new value to a let variable without mut?
  2. 2.
    Which declaration lets you change the value of x later?
  3. 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.

Finished reading? Mark it complete to track your progress.

On this page