Learning Rust · Part 0 · Chapter 01

The toolchain,
and the mindset

Before any syntax: get Rust onto your machine two ways, build something, and learn to read the compiler as a tutor instead of an adversary.

01

Why Rust, honestly

Every programming language is an argument about what matters. The systems engineer Bryan Cantrill puts it in terms of values: software platforms cannot honor every virtue at once, so each one quietly chooses a few to hold sacred and lets the others bend. C holds sacred your control over the machine, and in exchange it trusts you absolutely, including with the power to corrupt your own memory. The garbage-collected languages chose the opposite: they hold your safety sacred and, in exchange, took the machine away from you, hid the memory, added a runtime, accepted the pauses. For about forty years, systems programming treated this as a law of nature. You could know where every byte lives, or you could be protected from yourself. Not both.

Rust began as a quiet refusal of that law. When Graydon Hoare first presented the language publicly in 2010, his slide deck carried a subtitle that remains the best one-line history of the project: “technology from the past come to save the future from itself.” Almost nothing in Rust is new, and this is by design. Its ideas are salvage: ownership, destructors, sum types, and type inference were lifted from decades of research languages most working programmers have never run. What was new was the stubbornness of the synthesis. The discipline a careful C programmer carries in their head, the constant silent ledger of who owns this memory, who else may touch it, and when it dies, turned out to be writable. Rust writes that ledger into the type system, where the compiler audits it on every build. Discipline became code instead of folklore.

That is all “memory safety without garbage collection” actually means. If ownership is settled before the program runs, nothing needs to track it while the program runs: no collector, no pauses, no runtime tax, and yet no use-after-free, no data races, no null dereferences. The community's old slogan, fast, reliable, productive: pick three, is a boast, but a precise one. It does not claim Rust is pleasant. It claims a trade-off you have organized your career around no longer exists.

A philosophy this convenient should be made to pay rent, and by now it does, in public. Google, which ships Android in every pocket on earth, reports that after six years of writing new code in Rust, memory-safety bugs fell from 76% of Android's vulnerabilities to under 20%, with the new Rust code showing a vulnerability density roughly a thousandth of the equivalent C and C++, while spending less time in review and rolling back less often. The United States government, in its 2024 report “Back to the Building Blocks”, concluded that the cheapest place to fix the nation's most damaging class of exploit is the programming language itself. And for nearly a decade running, Stack Overflow's survey has found Rust the language its users most want to keep using, which is perhaps the strangest data point of all: the language famous for arguing with its programmers is the one they are least willing to give up.

Now the honest part. Rust will charge you for all of this, up front and in full. The compiler will reject programs you consider self-evidently fine, for reasons you will not at first understand. Compile times are slower than Go's. Most experienced programmers need weeks before they stop fighting the borrow checker and months before fluency, and during those weeks it is normal to feel demoted, like a senior engineer rendered junior by a type system. What that feeling actually is, though, is relocation. Every language makes you debug; they differ only in when. The null dereference you would have met in production at 3 a.m. has been moved to your editor, at 3 p.m., with a message attached. Rust did not invent the wall you hit at compile time. It moved the wall from the worst possible moment to the best one, and standing where the runtime bugs used to be is what practitioners keep describing, in oddly emotional language for an engineering community, as the ability to hack without fear.

The one-sentence pitch

Rust moves bugs from runtime to compile time, and charges you the learning curve as the price of admission. This corpus exists to lower that price.

None of which makes Rust the right tool for everything. A throwaway script, a prototype meant to die young, a CRUD service whose real bottleneck is the database: other languages get those done faster, and choosing Rust there is vanity, not engineering. Rust earns its keep where the things it holds sacred, correctness, performance, longevity, are the things your problem holds sacred too. That is what choosing a language by its values means.

The mindset, stated early

In most languages the cycle is: write, run, observe the crash, fix. In Rust the cycle is: write, read what the compiler tells you, fix, run, and it works more often than you are used to. The compiler is not a gate you sneak past; it is the pair programmer. Sections 06 and 07 train you to read it.

02

Choosing your editor

Before we install a compiler, furnish the room. This is not a detour. Programming is sustained thought, and the editor is the medium that thought moves through; every flicker of latency, every diagnostic you have to go looking for, every broken flow state is a tax paid in exactly the currency learning Rust will demand most. A beautiful, highly ergonomic environment is not an indulgence for people with opinions about fonts. It is load-bearing.

It matters doubly here, because of where Rust puts its teaching. The previous section promised that the compiler is the tutor; your editor is where the tutor speaks. With rust-analyzer (the official language server) wired in, the diagnostics you will learn to read in sections 06 and 07 appear inline as you type, inferred types surface when you hover, and the feedback loop between writing a line and understanding it collapses to nothing. The difference between that and reading compiler output in a terminal after the fact is the difference between a correspondence course and sitting beside the teacher.

The recommendation: Zed

Zed is this corpus's recommendation, and the reasons stack pleasingly. Start with the poetic one: Zed is itself written in Rust, top to bottom, by the team that previously built Atom and Tree-sitter. Its interface is drawn by a custom GPU-rendered framework, which is why it opens instantly and never stutters under load. Before you have written a single line of the language, you will be holding evidence of section 01's whole argument in your hands: this is what software feels like when the performance ceiling and the safety floor are the same height.

The practical reasons are stronger. Zed is open source and free, and it is genuinely feature-complete for this corpus and far beyond it: rust-analyzer comes wired in with zero configuration (open a .rs file and the inline tutor is simply there), and the editor has grown a real debugger, an integrated terminal, git support, and built-in real-time collaboration. Nothing about the path from chapter 1 to chapter 13 will require you to leave it or to assemble plugins.

Then there is the AI layer, and here this corpus wants to be precise, because Zed offers two very different things and only one of them belongs in your learning. The first is edit prediction: as you type, an open model proposes the small completion you were already mid-thought toward, and Tab accepts it. The second is a full agent panel that can be handed whole tasks and will write the code for you.

Tab, yes. Agent, not yet.

Use edit prediction freely while you learn: it operates at the granularity of your own intent. You conceive the line, it saves the keystrokes, and accepting or rejecting each small prediction keeps you the author, with the pattern landing in your head. Leave the agent panel closed for the duration of this corpus. Delegating to an agent now would outsource precisely the struggle that builds the mental model: you would be paying Rust's tuition and sending someone else to the lectures.

This is not craft superstition; the research has begun to catch up with the instinct, and the industry with the research. A randomized controlled trial published in PNAS gave high-school students unrestricted GPT-4 access during math practice: their practice scores soared by 48%, and when the assistant was taken away for the exam they scored 17% worse than students who never had it. The assistance felt like learning and was in fact its substitute (notably, a guardrailed tutor variant that gave hints instead of answers erased the harm). Closer to home, an ICER 2024 study of novice programmers found that generative AI tools widen the gap between strong and struggling students, leaving the struggling ones with an illusion of competence and a false sense of progress while the metacognitive skills programming is supposed to build never form. A CHI 2025 survey of 319 knowledge workers by Microsoft Research and Carnegie Mellon adds the mechanism: the more people trust the AI, the less critical thinking they perform, with effort shifting from doing the work to merely supervising it. And an MIT Media Lab EEG study (a preprint, but a careful one) measured the weakest brain connectivity and the poorest recall of their own text in participants who wrote with an LLM, a condition its authors call accumulated cognitive debt.

The pattern across all of it is the same: assistance that replaces effort feels like progress while quietly preventing it, and assistance that preserves effort (the hint, the small completion) does not. That is precisely the line between Tab and the agent panel. Agents are a power tool for the fluent, and the panel will still be there in chapter 13. This advice is offered without irony by an industry currently rather drunk on agents.

The terminal-native path: Neovim and tmux

If modal editing is already in your fingers, modern Neovim with an LSP rivals any IDE, and you should not hand-assemble a configuration from scratch on day one of a new language. The community maintains batteries-included distributions where rust-analyzer is a checkbox rather than a weekend: LazyVim (polished, the mainstream choice), AstroNvim (the most complete out of the box), NvChad (fast and theme-forward), and kickstart.nvim, which is not a distribution at all but a single, lovingly annotated init file you grow into your own, the pedagogically honest option for people who want to understand their tools the way this corpus wants you to understand Rust.

Pair it with tmux and the terminal-native path earns its keep: persistent sessions that survive a dropped SSH connection, your editor in one pane and a cargo check loop in the other, the identical environment on a server as on your laptop. This is the old Unix ethos, an environment composed from small sharp parts you own outright, and it is durable across decades in a way no editor company can promise. It says something about Zed, though, that a steady stream of lifelong vim users now run it with its first-class vim mode enabled: modal editing, plus the GPU rendering and the wired-in tooling, with nothing to maintain.

The IDE tradition, and a word about Emacs

Honesty requires naming the heavyweight: JetBrains RustRover brings decades of IDE craft to Rust, with the deepest debugging experience available, automated refactorings, and the test-runner ergonomics the IntelliJ family is known for, and it is free for non-commercial use, which learning is. If your muscle memory was formed in IntelliJ or PyCharm, RustRover will feel like home, and there is no shame in going home. (VS Code, for completeness, remains the reference host for rust-analyzer and is perfectly serviceable; this corpus simply thinks you deserve better than serviceable.)

And a quiet observation about longevity. The Emacs bindings, born in the 1970s, have outlived nearly every editor built since: ctrl-a, ctrl-e, and ctrl-k work right now in your shell's readline, in every text field on a Mac, and in Zed's selectable Emacs keymap. A tool's keystrokes becoming ambient infrastructure for half a century is what winning actually looks like. Emacs itself, with eglot and rust-analyzer, remains a serious modern Rust environment for those already fluent; if you are asking whether to learn Emacs first, the answer for this corpus is no, but the respect is due, and the bindings will follow you everywhere regardless.

EnvironmentCharacterPick it when
Zedwritten in Rust, GPU-fast, rust-analyzer and debugger wired in, Tab-prediction, vim and Emacs keymapsyou want the recommended default, and proof of chapter 1's thesis on your desktop
Neovim + a distromodal, terminal-native, infinitely yours, pairs with tmuxthe terminal is home and you intend to stay
RustRoverthe full JetBrains IDE: debugger, refactorings, test UXIntelliJ is your muscle memory; free for learning
Emacs + eglotthe self-extensible editor whose bindings outlived empiresyou already speak elisp

Set your editor for this book

This book is more than a set of pages. Every code example is a real file in the repository, and the in-browser Workbench lets you run and edit them as you read. Choose your editor on the configuration page; the choice is remembered and drives how examples open for you from here on.

The non-negotiables

Whichever you choose, two things are settled policy for this corpus: rust-analyzer runs inline, so the tutor speaks where you type; and while learning, completion over delegation. Tab is learning. The agent is outsourcing. Now, with the room furnished, let us install the tools.

03

Two ways to install

First, vocabulary. A Rust toolchain is a matched set: rustc (the compiler), cargo (build tool and package manager, the command you will actually type), the standard library, and companion tools. Toolchains come in channels: stable (a new release every six weeks; use this), beta, and nightly (experimental features).

Two good ways to manage toolchains. rustup is the official manager, assumed by every Rust document and CI recipe on the internet. mise is a general-purpose tool-version manager (it also manages your Node, Go, Python); if you already run it, it can own Rust too. Both are covered completely; pick one and skip the other, or read both and understand how they interact (they can coexist, with one gotcha covered at the end).

Path A: rustup, the official manager

install · rustup
# download and run the official installer (macOS / Linux)
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# accept the defaults: "stable" toolchain, "default" profile.
# then load cargo into the current shell:
$ source "$HOME/.cargo/env"          # bash / zsh
$ source "$HOME/.cargo/env.fish"     # fish

The default profile installs rustc, cargo, the standard library, offline documentation, and two tools you will meet in section 08: clippy (the lint engine) and rustfmt (the formatter). New shells pick up the PATH automatically; the source line is only for the shell you are sitting in.

Where things land, so nothing about your machine is mysterious:

LocationWhat lives there
~/.rustup/the toolchains themselves, one directory per channel/version, plus settings
~/.cargo/bin/thin proxy binaries for rustc, cargo, etc. that dispatch to the active toolchain; this is what goes on your PATH
~/.cargo/also: the package cache for downloaded dependencies, and binaries you install with cargo install

Day-to-day care and feeding:

maintain · rustup
# verify the install
$ rustc --version
rustc 1.87.0 (a1b2c3d4e 2025-05-12)
$ cargo --version
cargo 1.87.0 (e5f6a7b8c 2025-05-01)

$ rustup show              # what is installed, what is active, and why
$ rustup update            # update all toolchains AND rustup itself
$ rustup component add rust-analyzer   # the editor language server
$ rustup toolchain install nightly     # add a channel alongside stable
$ rustup default stable    # set the machine-wide default
$ rustup self uninstall    # remove everything cleanly

Per-project pinning is done with a rust-toolchain.toml file committed at the project root. Any rustup-managed cargo invocation inside that directory automatically uses (and if needed, downloads) the pinned version. This is the ecosystem convention for teams: clone the repo, get the right compiler, no meeting required.

rust-toolchain.toml
[toolchain]
channel = "1.87.0"          # or "stable", or "nightly-2025-05-01"
components = ["clippy", "rustfmt"]

Path B: mise, the polyglot manager

If mise already manages your other language runtimes, it can manage Rust with the same verbs. Under the hood mise delegates Rust installation to rustup, so the standard ~/.rustup and ~/.cargo layout from the table above still applies and rustup-aware tooling keeps working; what mise adds is its uniform per-directory version switching.

install · mise
# install mise itself, then activate it in your shell config
$ brew install mise
$ echo 'eval "$(mise activate bash)"' >> ~/.bashrc          # bash
$ echo 'mise activate fish | source' >> ~/.config/fish/config.fish  # fish

# install rust and make it the global default
$ mise use -g rust@latest

# verify
$ rustc --version
rustc 1.87.0 (a1b2c3d4e 2025-05-12)
maintain · mise
$ mise ls rust             # installed versions and which is active here
$ mise outdated            # is a newer rust available?
$ mise upgrade rust        # move to it
$ mise use rust@1.87       # pin THIS project (writes mise.toml)
$ mise uninstall rust@1.86 # remove an old version

Per-project pinning writes a mise.toml at the project root; entering the directory switches versions automatically:

mise.toml
[tools]
rust = "1.87"

mise can also be taught to respect the ecosystem's rust-toolchain.toml instead of its own file, which is the best of both worlds on team projects:

mise + rust-toolchain.toml
$ mise settings add idiomatic_version_file_enable_tools rust
The coexistence gotcha

If a project contains both a mise.toml pinning one version and a rust-toolchain.toml pinning another, the two managers can disagree about which compiler runs, and the answer depends on which shim is first in your PATH. Pick one owner per project: rust-toolchain.toml for anything shared (it is what the rest of the Rust world expects), mise.toml for personal projects if mise is your daily driver.

In plain English

Either path ends in the same place: cargo on your PATH and a stable toolchain installed. rustup is the official, universal answer; mise is the tidy answer if it already runs your machine. From here on, this corpus only ever says cargo, and never cares which manager put it there.

04

Anatomy of a new project

Rust projects are not assembled by hand; cargo generates and manages them. Make one:

terminal
$ cargo new hello
    Creating binary (application) `hello` package
$ cd hello

You get a complete, buildable project. Every file, explained:

what cargo new made
hello/
├── Cargo.toml      # the manifest: name, version, dependencies
├── .gitignore      # ignores /target (cargo new also ran git init)
└── src/
    └── main.rs     # the program. one file is all it takes

Cargo.toml is the project manifest, the equivalent of package.json or go.mod. The generated one is tiny:

Cargo.toml
[package]
name = "hello"
version = "0.1.0"
edition = "2024"

[dependencies]

Three notes on those lines:

  • edition is Rust's mechanism for evolving the language without breaking old code: each edition (2015, 2018, 2021, 2024) may change syntax rules, but crates of different editions link together freely. New projects get the newest edition; you never need to think about it again until you maintain old code.
  • [dependencies] is where libraries go. Rust calls a package a crate, and the public registry is crates.io. You will add your first dependency in a later chapter; the standard library carries this corpus a long way.
  • version follows semantic versioning; cargo takes semver seriously when resolving dependency upgrades.

Two more artifacts appear after your first build:

  • Cargo.lock: the exact resolved version of every dependency in the whole tree. Commit it for applications, so every machine builds the identical thing. It is machine-managed; you never edit it.
  • target/: all build output: compiled artifacts, incremental-compilation caches, dependency builds. It grows to gigabytes on real projects and is always gitignored. cargo clean deletes it; everything in it is regenerable.
05

Hello, world, properly

cargo new already wrote the program:

src/main.rs
fn main() {
    println!("Hello, world!");
}

Two observations an experienced programmer should make here, and one deliberate deferral:

  • fn main() is the entry point, like C or Go. No class wrapper, no boilerplate.
  • That exclamation mark is load-bearing. println! is not a function; it is a macro, code that generates code at compile time. That is how Rust gets type-checked, printf-style formatting with zero runtime cost. For now the rule is simply: name!(...) means macro, name(...) means function. Chapter 2 says more.
  • What "Hello, world!" actually is (a &str, and why Rust has more than one string type) is a chapter 10 story. Resist the urge; it will make sense in order.

Now run it, and meet the three cargo commands that make up your entire inner loop:

terminal
$ cargo run                # compile (if needed) AND execute
   Compiling hello v0.1.0 (/Users/you/hello)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.61s
     Running `target/debug/hello`
Hello, world!

$ cargo build              # compile only; binary lands in target/debug/
$ ./target/debug/hello     # it is a real, standalone executable
Hello, world!

$ cargo check              # type-check WITHOUT producing a binary: fastest
    Checking hello v0.1.0 (/Users/you/hello)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.09s

Notice the word dev in that output. Cargo builds in profiles, and the difference matters more in Rust than in most languages:

dev (default)release (--release)
Commandcargo buildcargo build --release
Optimizationsoff: compiles fast, runs slowfull: compiles slow, runs fast
Integer overflowpanics, loudlywraps around silently
Debug infoincludedomitted
Output pathtarget/debug/target/release/
Do not benchmark debug builds

The performance gap between profiles is routinely 10x to 100x, far larger than the debug/release gap you may know from other compiled languages. Every “Rust is slow” first impression that crosses the internet turns out to be a dev-profile benchmark. Measure with --release, always.

In plain English

Your loop is: edit, cargo check until the compiler is satisfied, cargo run to see it work, cargo build --release when you ship. check is the one you will run hundreds of times a day, which is exactly why the next section teaches you to read its output properly.

06

The compiler is the tutor

Here is the mindset shift this chapter exists to install. In most languages, compiler errors are terse rejections, and the real diagnosis happens later, at runtime, in a debugger or a stack trace. Rust inverts this: the error message is the diagnosis, and the Rust project treats diagnostic quality as a headline feature. Error messages are designed, reviewed, and continually improved like any other part of the compiler. Many contain the literal fix.

So the skill to build, starting now, is reading them slowly and completely, instead of pattern-matching “red text” and alt-tabbing to a search engine. Every rustc diagnostic has the same anatomy:

anatomy of a diagnostic
warning: unused variable: `name`          ← level and summary
 --> src/main.rs:2:9                      ← file:line:column (clickable in editors)
  |
2 |     let name = "world";                ← your code, quoted back
  |         ^^^^                           ← carets mark the exact span
  |
  = help: if this is intentional, prefix it with an underscore: `_name`
  = note: `#[warn(unused_variables)]` on by default

Read them in this order:

  1. The summary line. Level (error or warning) plus one sentence. Errors carry a code like E0308; warnings name the lint that fired.
  2. The span. The carets point at the precise tokens the compiler is unhappy about, often with a label explaining each marked region. This is where beginners stop reading. Do not stop.
  3. The help and note lines. This is the gold. help frequently contains the exact code change required; note explains the rule you ran into.

And when a sentence is not enough, every error code has an essay behind it:

terminal
$ rustc --explain E0308
Expected type did not match the received type.

Erroneous code examples:
...                        # a full page: what the error means, why the rule
                           # exists, and worked examples of fixing it
In plain English

Treat every diagnostic as a small lesson addressed to you personally: summary, exact location, suggested fix, and an essay on request. The discipline of reading to the bottom before touching the code is the single highest-leverage habit a new Rust programmer can build.

07

Three errors, read slowly

Theory done; let us practice on real diagnostics. Break your hello world three ways and read each message to the bottom. (Outputs are lightly abridged; type them and see the full versions.)

Break 1: a type mismatch

src/main.rs
fn main() {
    let x: i32 = "hello";   // an integer variable, a string value
    println!("{}", x);
}
cargo check
error[E0308]: mismatched types
 --> src/main.rs:2:18
  |
2 |     let x: i32 = "hello";
  |            ---   ^^^^^^^ expected `i32`, found `&str`
  |            |
  |            expected due to this

Notice it marks two spans: the carets under the wrong value, and a second label under i32 explaining why that type was expected. The compiler is showing its reasoning, not just its verdict. (&str is Rust's string-slice type; chapter 2 introduces it properly.)

Break 2: a typo

src/main.rs
fn main() {
    let greeting = "hello";
    println!("{}", greting);   // missing an 'e'
}
cargo check
error[E0425]: cannot find value `greting` in this scope
 --> src/main.rs:3:20
  |
3 |     println!("{}", greting);
  |                    ^^^^^^^ help: a local variable with a similar
  |                            name exists: `greeting`

The help line did not describe the problem; it solved it. A large fraction of everyday errors end exactly like this, with the fix printed in the message. This is why you read to the bottom.

Break 3: a glimpse of the main event

One more, chosen deliberately. You are not expected to understand why this is an error yet; you are practicing reading, and meeting the most famous diagnostic in Rust:

src/main.rs
fn main() {
    let s = String::from("hello");
    let t = s;            // in most languages: two variables, one string
    println!("{}", s);    // in Rust: this line is an error
}
cargo check
error[E0382]: borrow of moved value: `s`
 --> src/main.rs:4:20
  |
2 |     let s = String::from("hello");
  |         - move occurs because `s` has type `String`,
  |           which does not implement the `Copy` trait
3 |     let t = s;
  |             - value moved here
4 |     println!("{}", s);
  |                    ^ value borrowed here after move
  |
  = help: consider cloning the value if the performance
    cost is acceptable

Read the spans in order and the message tells a complete story across three lines of your code: the value's history (created at line 2, moved to t at line 3), the crime (using s after the move at line 4), and an escape hatch (clone). The words move, borrow, and Copy are the vocabulary of ownership, the one genuinely new idea Rust will ask you to learn. It gets chapters 3 and 4 to itself.

What just happened

Assigning s to t transferred ownershipof the string instead of copying it or sharing a reference to it. That rule, enforced at compile time, is the entire trick behind “no garbage collector, yet no use-after-free”. File the feeling away; chapter 3 builds the full mental model.

08

The toolbelt

Everything else the toolchain gave you, in the order you will come to rely on it:

CommandWhat it doesWhen
cargo checktype-checks without generating a binaryconstantly; it is the fast inner loop
cargo clippyhundreds of lints beyond the compiler: bugs, performance traps, unidiomatic stylebefore every commit
cargo fmtformats the whole project to the one canonical Rust style; no configuration debateson save, or before every commit
cargo doc --openbuilds browsable HTML docs for your project and every dependency, locallywhenever you would have searched the web
cargo testcompiles and runs the project's testschapter 11 makes this a habit
cargo add serdeadds a dependency to Cargo.toml from the command linewhen chapters start using crates

Run clippy on your hello world now, just to see it pass; its diagnostics read exactly like the compiler's, with the same anatomy you learned in section 06:

terminal
$ cargo clippy
    Checking hello v0.1.0 (/Users/you/hello)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.14s

Finally, close the loop with the editor you chose in section 02. If you took the Zed recommendation, rust-analyzer is already speaking: the diagnostics you just learned to read appear inline as you type, inferred types surface on hover, and completion reaches across your whole dependency tree. On the Neovim path, confirm your distribution has rust-analyzer enabled (each of the distros from section 02 makes it a one-line opt-in). With the language server live, most errors are read and fixed before you ever run cargo check by hand.

A note on style debates

There are none. cargo fmt output is the community style, full stop; CI on most Rust projects rejects unformatted code. Coming from languages with contested style guides, this is a quiet quality-of-life upgrade: the entire ecosystem reads the same.

09

Where we're headed

You now have a working toolchain, a project on disk, and the reading habit that makes the rest of this corpus tractable. The road from here, each chapter standing on the ones before it:

  1. The Toolchain and the Mindset: you are here.
  2. The Syntax Core: bindings, mutability, types, functions, expressions vs statements, control flow.
  3. Ownership: the full story behind that third error.
  4. Borrowing and Lifetimes: sharing values without giving them up.
  5. Structs and Enums: modeling data.
  6. Pattern Matching: taking data apart safely.
  7. Error Handling: panics, Result, and the ? operator.
  8. Monads: the chainable box behind Option, Result, and friends.
  9. Traits and Generics, 10. Collections and Iterators, 11. Modules, Crates, and Testing, 12. Smart Pointers, 13. Concurrency and Async.

One closing teaser, because you have already seen the evidence. In the third error, let t = s; did not copy a string or share a pointer; it moved the string, and the compiler then refused to let the old name touch it. Sit with how strange that is for a moment: an assignment that consumes its source. Every other language you know picked copying or sharing; Rust picked a third option nobody else dared to make the default, and that single choice is what buys the entire no-GC, no-use-after-free guarantee. Chapter 3 is about making that choice feel inevitable.

The whole chapter in six lines

  1. Rust's trade: a steeper compile-time wall in exchange for entire bug categories ceasing to exist at runtime.
  2. A toolchain is rustc + cargo + the standard library; manage it with rustup (official) or mise (polyglot), and pin projects with rust-toolchain.toml or mise.toml, one owner per project.
  3. cargo is the only command you type: new, check, run, build, clippy, fmt, doc.
  4. Debug builds are for iterating, release builds are for measuring; the gap is 10x to 100x.
  5. Diagnostics have an anatomy: summary, exact span, then help and note. Read to the bottom before touching code; the fix is often printed.
  6. let t = s; can move a value and retire the old name. That is ownership, the one genuinely new idea, and it is next after the syntax core.