Work in progress

The Rust We Need

Rust Guide R.0.0 — A Hands-on Route Through Ordinary Rust

This page is not a compressed Rust textbook. It is a route through the small part of Rust that this course needs. At each checkpoint, predict what a small program will do, ask the compiler, make one repair, and state what changed.

The exit test is being able to read this signature:

fn departure(
    train: &Train,
) -> Result<u32, DepartureError>

You do not need to memorize every spelling. You need to recognize the values and control flow well enough to reason with them.

Three resources, three different jobs

how a familiar operation is spelled.

unfamiliar idea.

programs to repair.

The compiler supplies evidence. It tests your explanation; it does not replace the explanation afterward.

Follow the official Rustlings setup once. Before the five experiments below, repair only this bootstrap set: intro2, variables1, and functions1, functions4, and functions5. They establish the edit–run loop, let, function calls, return types, and block values.

Read only the matching explanation when an idea is new:

  1. Functions

and blocks before experiment 1.

  1. User-defined types

before experiments 2–3.

  1. Shared references

before experiment 4.

  1. Result

and the ? operator before experiment 5.

Later Rustlings exercises often combine methods, mutation, strings, loops, and tests. Use them after these meetings rather than as prerequisites. Completing the rest can be a low-pressure semester practice lane. Its declarative-macro exercises can wait until after R.1–R.3.

How to use the meeting

Before each run:

  1. predict silently: compile, error, or output;
  2. compare with one neighbor;
  3. vote.

After the run, find the decisive line, make one repair, and state one rule. Everyone arrives at the run with a concrete claim, so participation does not depend on someone volunteering a question.

Five small experiments

The checked-in file is the runnable, repaired program. During the meeting, its source excerpts and a few temporary fragments isolate one idea at a time. The short fragments in experiments 3–4 are comparisons with the final file, not standalone programs to paste beside it.

1 · A semicolon changes the result

Runnable source The starting function examples/rust-00-reading-rust/src/main.rs Open lines 2–4
fn add_delay(scheduled: u32, delay: u32) -> u32 {    scheduled + delay}

Predict. Add a semicolon after scheduled + delay. Will the program compile?

Observe. The function promises a u32, but the semicolon discards the final expression's value and makes the body produce ().

Repair. Remove the semicolon.

Rule. A block's final expression, when it has no semicolon, is the value of that block.

2 · Structs and enums describe different shapes

Runnable source The train and status shapes examples/rust-00-reading-rust/src/main.rs Open lines 8–17
enum Status {    OnTime,    Late(u32),    Cancelled,}struct Train {    scheduled: u32,    status: Status,}

The declarations give every Train two fields, scheduled and status. Status declares three alternatives: OnTime, Late(u32), and Cancelled.

Start from this construction:

let late = Train {
    scheduled: 60,
    status: Status::Late(5),
};

Predict. Delete the status: Status::Late(5) line. Will Rust invent a default value?

Observe. The compiler reports a missing field.

Repair. Restore the field. Constructing a struct requires every field unless the code explicitly supplies another source for the omitted fields.

Rule. A struct contains all of its fields together. An enum value selects exactly one of its declared variants.

3 · Match every possible shape

Start with this ordinary function:

fn delay(status: Status) -> u32 {
    match status {
        Status::OnTime => 0,
        Status::Late(minutes) => minutes,
        Status::Cancelled => 0,
    }
}

Predict. Remove the Status::Cancelled arm. The first train is late, not cancelled. Will the function still compile?

Observe. It does not. delay accepts any Status, so match must account for every variant in the enum's closed choice. The pattern Status::Late(minutes) exposes the number stored in that variant.

Repair. Restore Status::Cancelled => 0.

Rule. A match over an enum must cover every possible variant.

The function now compiles, but it treats cancellation exactly like an on-time train. Experiment 5 will repair that meaning.

4 · Borrow instead of move

Suppose we wrote:

fn scheduled(train: Train) -> u32 {
    train.scheduled
}

println!("{}", scheduled(late));
println!("{}", scheduled(late));

Predict. Which call fails?

Observe. The second call fails because the first moved the Train into the function.

Repair. Change the parameter to train: &Train and both calls to scheduled(&late).

Rule. &Train gives shared read access. The function can inspect the train without taking it from the caller, so both calls can use the same value.

5 · Give failure its own shape

Runnable source The fallible implementation examples/rust-00-reading-rust/src/main.rs Open lines 21–36
enum DepartureError {    Cancelled,}fn delay(status: &Status) -> Result<u32, DepartureError> {    match status {        Status::OnTime => Ok(0),        Status::Late(minutes) => Ok(*minutes),        Status::Cancelled => Err(DepartureError::Cancelled),    }}fn departure(train: &Train) -> Result<u32, DepartureError> {    let minutes_late = delay(&train.status)?;    Ok(add_delay(train.scheduled, minutes_late))}

Zero minutes is a successful on-time departure. Cancellation is not a departure time. The final delay therefore returns Result<u32, DepartureError> instead of using the fabricated value 0 for cancellation.

Result<T, E> is an enum with two shapes: Ok(T) for success and Err(E) for failure. Here they are Ok(u32) and Err(DepartureError). Because delay borrows its Status, minutes is seen through that borrow; *minutes copies the stored u32.

Now consider this line:

let minutes_late = delay(&train.status)?;

Predict. Remove the ?. Why can add_delay no longer use minutes_late?

Observe. Without ?, minutes_late is a complete Result<u32, DepartureError>, not the successful u32.

The ? is the compact form of this decision:

let minutes_late = match delay(&train.status) {
    Ok(minutes) => minutes,
    Err(error) => return Err(error),
};

Repair. Restore the ?.

Rule. ? extracts the successful value or returns the error from the current function.

Run the repaired program

Runnable source The repaired program uses the same train twice examples/rust-00-reading-rust/src/main.rs Open lines 40–67
fn show(result: Result<u32, DepartureError>) {    match result {        Ok(minutes) => println!("leaves at minute {minutes}"),        Err(DepartureError::Cancelled) => println!("cancelled"),    }}fn main() {    let late = Train {        scheduled: 60,        status: Status::Late(5),    };    show(departure(&late));    show(departure(&late));    let on_time = Train {        scheduled: 90,        status: Status::OnTime,    };    show(departure(&on_time));    let cancelled = Train {        scheduled: 120,        status: Status::Cancelled,    };    show(departure(&cancelled));}

From the notes repository root:

cargo run \
  --manifest-path \
  examples/rust-00-reading-rust/Cargo.toml

The output is:

leaves at minute 65
leaves at minute 65
leaves at minute 90
cancelled

The first two lines show that shared access preserved the same late train for a second read. The last two distinguish a successful on-time departure from a failure.

Read the exit test

fn departure(
    train: &Train,
) -> Result<u32, DepartureError>

departure temporarily reads a Train and returns one Result: either Ok(minute), carrying a nonnegative u32, or Err(error), carrying a DepartureError. Because it borrows rather than takes the train, the caller can use that train again.

Ready for R.1

So far, trains, statuses, integers, and results have all been ordinary runtime values. We used println!(...) as a provided output operation and learned only to recognize the name!(...) macro-invocation spelling.

R.1 asks the course-specific question that these general introductions do not: what does a macro receive and return when Rust code itself becomes its data?