Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

How this book is built

Rules for anyone adding a page. They exist because a book is a second copy of the language, and the usual fate of a second copy is to drift until it teaches a version of the language nobody ships.

What a page may contain

Prose, and code that something else already verifies. Nothing else.

You want to showWrite it as
A few lines from a real program{{#include ../path/prog.rk:anchor}}
A small self-contained snippetan inline block with <!-- test: compile --> or <!-- test: run | expected output -->
What the compiler says when you get it wrong{{#include ../../errors/<chapter>/<case>.out}}
What a program prints{{#include ../../../../tests/golden/<name>.out}}

<!-- test: parse --> does not count as verified anywhere in the book. Parsing proves the syntax is current; syntax is not what rots. The front-page snippet passed test: parse while calling fs.open, which the real grep program doesn’t use, and missing both its imports, so a reader copying it got two errors on page one. compile type-checks; run | expected runs it and matches the output. Use one of those.

CI runs the markers in its “Docs snippets parse” step, which has covered docs/ for a while. What it can’t tell you is whether a block has a marker at all, or whether the one it has is strong enough, which is what tests/book_gate.sh is for. It also checks that every include resolves: the file is present and the named anchor really is in it, because mdBook renders a missing anchor as nothing at all and says so quietly.

Errors are content

Chapters teach with real compiler output. Each case is a program under docs/book/errors/<chapter>/ that must not compile, with its rask check output pinned beside it. The gate re-renders and diffs on every run, so improving a diagnostic shows up as a diff on the page that teaches it, which is the review you want. A case that starts compiling is a hard failure, not a stale golden: the page is claiming a rejection the compiler no longer makes.

Regenerate after a deliberate diagnostics change:

tests/book_gate.sh --update

How to explain it

The machine is the metaphor. Describe what actually happens in memory, in plain words, and don’t build a story on top of it.

Lending is the cautionary case. In a library the book leaves the shelf; in Rask the caller goes on reading the value for the whole call. A reader who builds the library model predicts the wrong thing at their first mutate call — and learned it here.

Rask can afford the literal route where most languages can’t. No lifetime annotations, no reference type, no effects: every rule is a claim about bytes and where they sit. So make the claim.

One noun carries it: storage. A place in memory holding a value. A name reaches storage, a move hands it over, a borrow reads storage the function doesn’t own, a copy makes a second one. Define it once and don’t introduce a parallel vocabulary three chapters later.

Idea first, word second. Build the model in plain language, then attach the term in one sentence, as a hook for the error messages:

A function that takes a value without take reads the caller’s storage where it sits — it doesn’t get a copy, and it doesn’t get to keep it. The compiler calls this borrowing, and you’ll see the word in error messages.

Jargon is a label for something the reader already has. Arriving before the idea is what makes a language feel like it was written for people who already know it.

Spend a metaphor only where the literal thing isn’t observable. In Rask that’s almost nowhere, which is the design working rather than a style choice. Every spend should feel like a defeat.

The test for a paragraph: could a reader predict the next error message from what it just told them? If they’d have to re-derive it from a metaphor first, the paragraph is doing the wrong job.

No “I”, and no decisions. The style guide keeps first person for design writing, where someone is owning a tradeoff. A chapter isn’t that. It explains a language that already works, to someone trying to use it, so it says what the rule is and why it holds: “taking a lock is a real cost, so it belongs in the source where it happens”, never “I picked scoped access over making a shared value feel like a plain one”. Same reasoning, no author in the room.

The difference from the section below is worth keeping straight. The why belongs in a chapter. The choosing doesn’t: alternatives that were weighed, what was rejected and what it would have cost are design writing, and they live in specs/ and RULINGS.md. A reader learning Shared needs the reason the lock is visible. They don’t need to know it was ever up for debate.

Write the language, not the compiler’s current mood

A chapter describes the language as it’s designed. A bug in one backend is a bug, not a smaller language, and it never decides what a page is allowed to teach.

When the two backends disagree the interpreter is the reference, so a block that runs correctly there is correct. Mark it <!-- test: run-interp | expected -->, which exists for this, and file the native gap. The block stays verified, the chapter stays honest, and the marker is what the fix later deletes.

What this rules out is quietly teaching less. The scoped-access chapter dropped recursive Heap for a day because the recursive case doesn’t lower on native (#1234), and what the page taught instead was a smaller language that happens to match one backend’s bugs. A reader can’t tell the difference between “Rask doesn’t do this” and “Rask does this and the compiler is behind”, and only one of those is true.

Caveats about what’s broken don’t belong in a chapter either. They date instantly and they’re the issue tracker’s job.

The book teaches the ruling, not just the rule

The specs are normative and say what the rule is. A chapter’s job is the part the spec tables leave out: why it holds. “The marker is required” is a spec line. “A misread move is caught for you and a misread mutation isn’t, so the one that can’t be caught is the one you write down” is a chapter.

Note what that example doesn’t do: it never mentions the alternative, or that anyone chose. It gives the reader the reason and stops. That’s the line between a why, which belongs here, and a decision, which belongs in specs/.

specs/RULINGS.md is where those arguments come from. If a chapter can’t say why, it’s restating the spec and should link to it instead.

Size is capped by the language, not by the author

specs/DAY_ONE.md holds the reading set: the concepts you need to read someone else’s Rask, which must fit one page. The guide inherits that budget: roughly one chapter per Day-One concept, and no chapter for something that isn’t in the language yet.

This is the answer to books that grow to a thousand pages: the guide can’t outgrow the language, because its table of contents is the language’s own list. Anything deeper goes to specs/, which is allowed to be long.

What to do when the language changes

Nothing, usually. That’s the point: included code moves with its program, error renderings are regenerated by the gate, program output comes from the goldens. The prose only needs touching when a decision changes, which is rare and deliberate.

If you find yourself keeping a page in step by hand, the page is built wrong.