---
title: "1.11 Expressions and statements"
description: "Rust is an expression-oriented language: blocks produce values, semicolons discard them, and the unit type fills the gaps."
url: "https://learnrust.net/chapter-1/expressions-and-statements/"
last_updated: "2026-06-11"
---

# 1.11 Expressions and statements

This is the most important lesson in the chapter. It looks like grammar trivia. It is actually the master key to Rust's design, and chapters from here to the end of the course will quietly lean on it.

## Expressions

An **expression** is any piece of code that produces a value. Working out that value is called **evaluating** the expression. You've been writing expressions all chapter:

- `5` evaluates to 5 (literals are the simplest expressions)
- `x` evaluates to whatever `x` holds (using a variable is an expression)
- `x + 1` evaluates to one more than that (operators build bigger expressions from smaller ones)
- `name.trim()` evaluates to the trimmed text (so do most function-ish calls)

Expressions nest: in `(x + 1) * 2`, the inner expression's value feeds the outer one. Wherever Rust expects a value, any expression of the right type can stand there; that substitution rule is what makes them composable, like Lego studs.

## Statements

A statement (lesson [1.1](https://learnrust.net/chapter-1/statements-and-program-structure/)'s oldest term, now in sharper focus) is an instruction that performs an action but doesn't produce a value for surrounding code. The `let` definition is the statement you know best: `let x = 5;` *contains* the expression `5`, but the statement as a whole just performs the action "create x". You can't write `let y = (let x = 5);`, because there's no value there to use.

And here's the bridge between the two worlds: take any expression, add a semicolon, and you get an **expression statement**: the expression is evaluated, and its value is *thrown away*.

```rust
x + 1;
```

That's legal Rust. It computes one more than `x`, discards the result, and accomplishes nothing (the compiler will warn you it's pointless). Which reveals the semicolon's true job in Rust. It isn't just a sentence-ending period; it's the *discard* marker. `println!("hi");` is an expression statement too: do the printing, discard the (useless) result, move on.

So what's the result of a `println!`? That needs one more character of vocabulary.

## The unit type

Rust has a type for "nothing useful here," written `()` and called the **unit type**. It has exactly one value, also written `()`. Printing produces it. Assignment produces it (this is why `a = b = 5` from last lesson isn't a thing). Any expression that exists for its side effects, rather than to compute something, evaluates to `()`.

You'll rarely write `()` on purpose this early, but you'll *see* it in error messages constantly, and now you can read it: "found `()`" means "that thing produced nothing useful."

## Blocks are expressions

Now the payoff. A pair of braces containing code is a **block**, and in Rust, a block is an expression. Its value is the value of the final expression inside it, the one *without* a semicolon, called the block's **tail expression**:

```rust
fn main() {
    let y = {
        let a = 2;
        a * 3
    };
    println!("{y}");
}
```

```
6
```

Read the block like Rust does: run the statements in order (`let a = 2;`), then evaluate the tail expression (`a * 3`, no semicolon) and hand that value out as the block's result. The whole `{ ... }` evaluated to 6, so `y` is 6.

Now watch one semicolon change the meaning. Put one after `a * 3` and there's no tail expression anymore, just statements; the block's value becomes `()`, and the program stops compiling, because `y` is now `()` and `println!` has no idea how to display "nothing useful":

```
error[E0277]: `()` doesn't implement `std::fmt::Display`
 --> src/main.rs:6:15
  |
6 |     println!("{y}");
  |               ^^^ `()` cannot be formatted with the default formatter
  |
  = help: the trait `std::fmt::Display` is not implemented for `()`
  = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
```

(The message mentions traits and formatters, chapter 16 machinery, but you can already read the headline: `y` is `()`, and `()` isn't printable text.)

> **Warning**
>
> Semicolon discipline, then: inside a block, a semicolon at the end of the *last* line is the difference between "this block produces that value" and "this block produces `()`". A stray or missing final semicolon is about to become your most common one-character bug, and now you know exactly what it does.

This also pays off the trick question from lesson [1.1](https://learnrust.net/chapter-1/statements-and-program-structure/)'s quiz. `fn main() { println!("Hi there") }` compiles because the `println!` with no semicolon is the body's tail expression. Its value is `()`, and `()` happens to be exactly what `main` is expected to produce. No semicolon, no crime, by complete coincidence of types.

> **Key insight**
>
> Languages in the C family are statement-oriented: code is mostly instructions, and values live only inside them. Rust is **expression-oriented**: nearly everything produces a value, including blocks and (as you'll see) entire `if`s and `match`es. The practical habit to build is asking not just "what does this code *do*?" but "what does this code *evaluate to*?" Lesson 4.7 will let you write `let x = if ... `; lesson 2.2 will show function bodies are just blocks whose tail expression is the return value. Both are this lesson wearing different hats.

## Quiz time

**Question #1**

Classify each line: statement (not an expression statement), expression statement, or expression?

a) `let width = 80;`
b) `width + 20;`
c) `width + 20`
d) `println!("{width}");`

<details class="solution">
<summary>Show solution</summary>

a) A statement (a `let` definition; produces no value).
b) An expression statement: evaluates `width + 20`, then the semicolon discards the 100. (Pointless, and the compiler warns about it.)
c) An expression. On its own line it could be a block's tail expression, producing 100.
d) An expression statement: prints, then discards the `()`.

</details>

**Question #2**

What does this program print?

```rust
fn main() {
    let x = 4;
    let y = {
        let x = x + 1;
        x * 2
    };
    println!("{x} {y}");
}
```

<details class="solution">
<summary>Show solution</summary>

```
4 10
```

Inside the block, a *new* `x` (worth 5) exists, and the tail expression `x * 2` makes the block evaluate to 10. The outer `x` is untouched, still 4. (Two variables named `x`? That's shadowing, lesson 5.2, but the block-value machinery is pure 1.11.)

</details>

**Question #3**

Predict the compiler error:

```rust
fn main() {
    let z = {
        let a = 6;
        a + 1;
    };
    println!("{z}");
}
```

<details class="solution">
<summary>Show solution</summary>

The semicolon after `a + 1` means the block has no tail expression, so it evaluates to `()`, so `z` is `()`, and `println!("{z}")` fails with E0277: `()` doesn't implement `Display`. Delete that one semicolon and the program prints 7.

</details>

That's the deep idea of the chapter in your pocket. Time to spend it: in the next lesson you'll build a complete program from scratch, wrong turns included.

## Sitemap

See the full [sitemap](https://learnrust.net/sitemap.md) for all pages.
