---
title: "Rust's Design Goals"
source: 'https://academia.sh/en/courses/rust-fundamentals/design-goals'
course: 'Rust Fundamentals'
language: en
updated: '2026-09-15T04:58:45+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Defines the expression, statement and item classes by the value-producing criterion and distinguishes them from one another'
    - 'Verifies the class distribution of eighteen writings by running the measurement core'
    - 'Explains why two methods that do the same work fall into separate classes for a reason read from their signature'
---

# Rust's Design Goals

A measurement core of eighteen writings runs from inside the program and places each writing into one of three classes — expression, statement, item; the result is fixed at eight expressions, five statements, five items, and the rest of the course carries this trio forward as a single question: does what is written in the source produce a value?

The Go curriculum closed with five courses, and its last course measured what can be known
about a program itself: whether a piece of information comes from the source, from a tool,
or from the run. This curriculum takes one more step in the same direction and looks directly
at the source. When a line is written in Rust, that line's fate splits three ways: the line
**is evaluated and produces a value**, **is evaluated but produces nothing**, or **is never
evaluated at all, only declares a name**. The one question asked throughout the course is
this: does what you write in the source produce a value?

This question has been asked in two different forms before. The Java Fundamentals course
measured what the compiler **adds** to the source — how something not written in the source
gets completed during compilation. The Go Fundamentals course, by contrast, measured the
counterpart of something **not being written** — whether the language leaves that defined,
rejects it, or leaves it open. This course departs from both: here nothing is missing, and
the matter is what is produced when what is **written** in the source gets evaluated.

This threefold classification should be read as a design goal, not as a list of three
categories to recite from memory: which of the three classes you write the same piece of work
in determines where that line can be used. You can bind an expression to a variable, pass it
to a function, or embed it inside another expression; a statement fits none of these, because
all you have in hand is `()`. The course's later lessons will show, one by one, where this
boundary is useful — and where it turns into a writing mistake.

## Expression, Statement, Item

There are three answers, and these three names do not change throughout the course.

| Class | Definition |
|---|---|
| expression | Evaluates and produces a value other than `()` |
| statement | Evaluates but produces `()` — or is never evaluated |
| item | Only declares; it is not itself evaluated, and its value arises when used |

The `()` that appears in the middle of this trio is not a gap — the Primitive Types lesson
takes it up separately, but what needs saying already is this: `()` is itself a type, and it
has exactly one value. A statement does not "produce nothing"; it produces the value of this
single-valued type. `if`, `match`, `loop`, and a block are each a value in Rust; a semicolon
**discards** a value; `fn`, `struct`, `enum`, `mod`, and `const` are never evaluated at all —
they only introduce a name to the compiler. Because these three paths exist, this question
can carry an entire course.

The Programming Fundamentals course built the `if` construct with Python; there, `if` was a
statement and produced no value. This concept is not rebuilt here — this one-sentence
reference exists only to show why the three classes form a separate axis in Rust: the same
construct does not fall into the same class in every language.

## The Measurement Core

The core used throughout the rest of the course is `expression.rs`. It runs eighteen separate
writings inside the same frame, writes each one to an observation log, and prints the total
per class at the end. The observation log itself is an array of tuples — each row holds a
name, a class, and a value together as three separate pieces of text. A tuple is the most
direct way to carry several values of different types as a single unit; the fact that all
three fields here are text is not a requirement, but a choice made so the table can be printed
in one uniform format. The observation log is not part of the language — this should be kept
in mind while reading the code.

**LB1.** The `observe` function and the `log` tuple (a `String`, `String`, `String` triple) are
the core's own design decision, not a mechanism of Rust. What decides which class a writing
falls into is each line's form in the source; the `observe` call only records this decision
into a table so the eighteen lines can be compared within a single run.

```rust
// expression.rs — everything written in the source falls into one of three classes:
// one that produces a value (expression), one that produces () or is never
// evaluated (statement), one that only declares (item).

use std::collections::BTreeMap;
use std::collections::HashSet;

const LIMIT: i32 = 100;

struct Point {
    x: i32,
    y: i32,
}

enum Color {
    Red,
    Green,
}

mod measure {
    pub fn unit() -> i32 {
        3
    }
}

fn double(x: i32) -> i32 {
    x * 2
}

fn observe(log: &mut Vec<(String, String, String)>, name: &str, class: &str, value: String) {
    log.push((name.to_string(), class.to_string(), value));
}

fn main() {
    let mut log: Vec<(String, String, String)> = Vec::new();

    // --- expression: writings that produce a value ---

    let a = 7;
    let b = if a > 5 { "big" } else { "small" };
    observe(&mut log, "if as a value", "expression", b.to_string());

    let color = if a > 5 { Color::Green } else { Color::Red };
    let name = match color {
        Color::Red => "red",
        Color::Green => "green",
    };
    observe(&mut log, "match as a value", "expression", name.to_string());

    let block = {
        let inner = double(a);
        inner + 1
    };
    observe(&mut log, "block as a value", "expression", block.to_string());

    let mut counter = 0;
    let loop_result = loop {
        counter += 1;
        if counter == 4 {
            break counter * 10;
        }
    };
    observe(&mut log, "loop returns a value via break", "expression", loop_result.to_string());

    let n = Point { x: 1, y: 2 };
    observe(&mut log, "struct syntax produces a value", "expression", format!("x={} y={}", n.x, n.y));

    let v = vec![1, 2, 3, 4];
    observe(&mut log, "the vec macro produces a value", "expression", format!("length={}", v.len()));

    let total: i32 = v.iter().map(|x| double(*x)).sum();
    observe(&mut log, "an iterator chain produces a value", "expression", total.to_string());

    let mut set: HashSet<i32> = HashSet::new();
    let first = set.insert(5);
    let second = set.insert(5);
    observe(&mut log, "set insertion returns a value", "expression", format!("{} then {}", first, second));

    // --- statement: writings that produce () or are never evaluated ---

    let with_semicolon = {
        double(2);
    };
    observe(&mut log, "a block ending in a semicolon", "statement", format!("{:?}", with_semicolon));

    let mut d = 1;
    let assignment = { d = d + 8; };
    observe(&mut log, "the assignment itself", "statement", format!("{:?} (d={})", assignment, d));

    let mut list = Vec::new();
    let push_result = list.push(d);
    observe(&mut log, "pushing onto a vector", "statement", format!("{:?} (length={})", push_result, list.len()));

    let mut iterations = 0;
    let for_result = for _ in 0..3 {
        iterations += 1;
    };
    observe(&mut log, "the for loop", "statement", format!("{:?} (count={})", for_result, iterations));

    let print_result = print!("");
    observe(&mut log, "the value of printing", "statement", format!("{:?}", print_result));

    // --- item: writings that only declare; their values arise when used ---

    observe(&mut log, "fn declaration", "item", format!("declaration; call gave {}", double(3)));
    observe(&mut log, "struct declaration", "item", format!("declaration; instance gave x={}", n.x));
    observe(&mut log, "enum declaration", "item", "declaration; variant gave green".to_string());
    observe(&mut log, "mod declaration", "item", format!("declaration; call inside it gave {}", measure::unit()));
    observe(&mut log, "const declaration", "item", format!("declaration; reading it gave {}", LIMIT));

    // --- report ---

    println!("{:<40}{:<12}{}", "observation", "class", "value");
    for (name, class, value) in &log {
        println!("{:<40}{:<12}{}", name, class, value);
    }

    let mut tally: BTreeMap<&str, i32> = BTreeMap::new();
    for (_, class, _) in &log {
        *tally.entry(class.as_str()).or_insert(0) += 1;
    }
    println!();
    for (class, count) in &tally {
        println!("{:<12}{}", class, count);
    }
    println!("total       {}", log.len());
}
```

```
observation                             class       value
if as a value                           expression  big
match as a value                        expression  green
block as a value                        expression  15
loop returns a value via break          expression  40
struct syntax produces a value          expression  x=1 y=2
the vec macro produces a value          expression  length=4
an iterator chain produces a value      expression  20
set insertion returns a value           expression  true then false
a block ending in a semicolon           statement   ()
the assignment itself                   statement   () (d=9)
pushing onto a vector                   statement   () (length=1)
the for loop                            statement   () (count=3)
the value of printing                   statement   ()
fn declaration                          item        declaration; call gave 6
struct declaration                      item        declaration; instance gave x=1
enum declaration                        item        declaration; variant gave green
mod declaration                         item        declaration; call inside it gave 3
const declaration                       item        declaration; reading it gave 100

expression  8
item        5
statement   5
total       18
```

**LB2.** The run is deterministic: the same source produces the same eighteen lines, in the
same order, with the same class distribution, on every run. No line reads from a file system,
a clock, or a random generator — everything is computed from a fixed source. Eight of the
eighteen writings are expressions, five are statements, and five are items; this count does
not change for the rest of the course — only each lesson takes its own slice of it.

The final block, which computes the total per class, uses an ordered map (`BTreeMap`), not a
hash map (`HashMap`). This choice is not arbitrary: the order in which a hash map's keys are
traversed can vary from run to run, so even if the total for all three classes comes out
correct, the order in which the rows print is not guaranteed. An ordered map's keys always
traverse in the same order — here, alphabetically, `expression`, `item`, `statement` — so the
printed text also stays deterministic. This is the most concrete example of the course's rule
about environment-dependent data: if a collection's order is going to be used as a
presentation device, that order must come from a structure that carries a guarantee. This
distinction will be its own subject in the Ordered and Set Structures lesson; here it only
explains why the core's own output can be safely compared run after run.

## Reading the Results

What the eight expression lines have in common is that all of them stand on the right side of
a `let` binding and produce a value: `if`/`match` picks a branch and gives that branch's
value, a block gives its own last line, `loop` gives only the value handed to `break`, struct
syntax and the `vec` macro construct a new value, an iterator chain accumulates a total, and
set insertion returns a `bool`. The five statement lines show the exact opposite: a block
closed with a semicolon, an assignment, `Vec::push`, a `for` loop, and an empty print — all
five print `()`, because all five say "do a piece of work," not "give a value." The five item
lines stand on a separate axis: the `fn`, `struct`, `enum`, `mod`, and `const` declarations
themselves never enter the table at all; what enters the table is the value that arises from
**using** these declarations — the `6` that `double(3)` returns, a field of the `Point`
instance, the call to `measure::unit()`, the reading of `LIMIT`. A declaration produces
nothing the moment it is written; it only introduces a name, and that name produces a value
once it is used in a later line.

**LB3.** The `double` item is used in three separate places in the core: once inside `block`
(to produce the value that feeds the `inner + 1` computation), once in the iterator chain to
apply to each element, and once in the "fn declaration" line, called directly so its result is
written to the table. In all three, `double` itself does not change — what changes is the
argument given to each call and the value that call returns. This makes concrete what it means
for an item to be "valueless": the declaration is single, but the number of uses and the value
each use produces are free.

The count is not equal among these three groups, and it is not expected to be: most of the
source is written to produce a value (8), and the rest is written either for a side effect (5)
or to introduce a name (5). The rest of the course will deepen each of these three groups in
turn — `if`, `match`, `loop`, and the full set of loops in the Control Flow lesson, assignment
and binding in the Variables and Immutability lesson, and the share declarations hold in the
source in the Setup and the Package Manager lesson. The only question this lesson leaves open
is where the class is read from.

## Limiting Measurement: Class Is Read From the Signature

Two rows that sit side by side in the table carry the answer to this question: "set insertion
returns a value" (expression, `true then false`) and "pushing onto a vector" (statement,
`() (length=1)`). The two look like they do the same work — adding an element to a collection.
But one produced a value during the run, the other did not. The difference lies not in the
nature of the work, but in the signature of the method being called.

`HashSet::insert` both changes the set and returns a boolean saying whether the insertion
actually added a new element. In the core, `5` was inserted twice: the first call returns
`true` because it adds a new element to the set, the second call returns `false` because it
tries to insert the already-present `5` again — the `true then false` in the table is these
two calls placed side by side. `Vec::push` never gives this kind of information: because
adding an element to a vector always succeeds, there is no boolean to return, and the
signature gives only `()`.

This shows that a single piece of work called "insertion" gives two separate Rust methods two
separate classes. What determines the class is not the reader's intuition about what the work
is, but the return type the compiler sees: statement if the return type is `()`, expression if
it is any other type. Because this criterion is formal and mechanical, which class a method
falls into can be told in advance without looking at its call, just its signature — a mutator
that returns `Option` or `bool` is always an expression, a mutator that returns only `()` is
always a statement. This distinction will come up once more in the Collections lesson, with
`Vec::pop` returning `Option`.

This also explains why the course stands under the heading of a design goal: class is not a
runtime observation but a signature decision, and a signature can be read from the source
without running anything. A reader can look at `HashSet::insert`'s return type and say, without
running a single instance, that it is an expression. The standard library's own design
determines this classification in advance; what the lesson measures throughout is what
difference this determination makes when reading the source.

## Summary

- Everything written in the source falls into one of three classes: an expression produces a
  value, a statement produces `()` or is never evaluated, and an item only declares a name
  whose value arises from use.
- `()` is not a gap; it is a single-valued type, and it is what a statement produces.
- The measurement core classifies eighteen writings in a single run: 8 expressions, 5
  statements, 5 items — this count does not change for the rest of the course.
- The same piece of work (insertion) can give two separate methods two separate classes; class
  is read not from the nature of the work but from the method's return type — its signature.

## Next Step

The three classes are now defined, but for a source to actually run, there is a much smaller,
much more mandatory set than these: the least a program must write. The next lesson asks about
this smallest set — it shows how, when a piece kept in a separate file that does not live in
the source itself changes, the output changes while the source itself never does.
