Rust for Systems: Read a SQLite File · lab 01 of 11

[ free ] [ cargo · reading a diagnostic ]
01

Fix six errors and recognise a SQLite file

[ preview ] The lesson is free to read — building it needs a free account.

You'll learn: the loop you'll run a few hundred times over the rest of this course — cargo check, cargo test — and the one skill that decides whether Rust feels like a collaborator or a gatekeeper: reading a compiler diagnostic all the way to the end.

Why this is stage 01 and not an appendix

Here is the thing nobody says on day one, so it lands as a personal failing instead of a fact about the tool:

Not compiling is the normal state of writing Rust. Experienced Rust programmers don't write code that compiles first try. They write code, get rejected, read the rejection, adjust — dozens of times an hour.

Rust front-loads work other languages defer. C compiles a program that segfaults next Tuesday; Python runs right up to the AttributeError in production. Rust moves both to the one moment they're cheap to fix — while you're looking at it. The price is a compiler that talks back, which at first feels like being told off.

It isn't. A diagnostic is closer to a code review than a rejection letter: it names the line, underlines the expression, says what it expected and what it found, and often writes the fix out for you. Beginners who stall are rarely the ones who can't write Rust — they're the ones who read the first line, feel bad, and start guessing. The ones who get fluent read the whole thing, help: included. So this stage is deliberately about failure: your file ships broken, and the job is to satisfy the compiler six times in a row.

The loop

Everything in this course is one cargo crate, sqlkit, and two commands.

cargo check    # type-check only, no machine code — the fast loop while a file is red
cargo test     # build, then run every #[test] in the crate — the real verdict

cargo test prints a line per test and a summary:

running 5 tests
test warmup::tests::magic_is_sixteen_bytes ... ok
test warmup::tests::short_slice_is_not_sqlite ... ok
test warmup::tests::notadb_is_not_sqlite ... ok
test warmup::tests::tiny_db_is_sqlite ... ok
test warmup::tests::describe_reports_kind_and_length ... ok

test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

Look twice: those lines are also the evidence this course is graded on. What the grader reads is what your run printed — so a claim you never asserted is one nobody, including you, knows is true.

One thing this course never stops to do, and it is better said now than discovered: it does not teach you how a variable, a function or a loop is written. It teaches the Rust that is peculiar to Rust — ownership, match, Result, lifetimes, traits — and takes the rest the way you already read it in whatever language you use today. Where a spelling is genuinely new — &[u8], String against &str, format!, the #[cfg(test)] block that will live at the bottom of every file you write — Rust, spelled out gives it four lines and one sentence on what is different, and nothing more. Keep it open beside this lab. It is not graded and never will be.

Anatomy of a diagnostic

Two of them, both verbatim rustc 1.97.1 output. The first is from a two-line file written for this section, so that every part of the format is on screen at once:

let n = bytes.length();

and what rustc says about it:

error[E0599]: no method named `length` found for reference `&[u8]` in the current scope
 --> src/lib.rs:2:19
  |
2 |     let n = bytes.length();
  |                   ^^^^^^
  |
help: there is a method `len` with a similar name
  |
2 -     let n = bytes.length();
2 +     let n = bytes.len();
  |

Five parts, each doing a job:

  1. error[E0599] — severity, then a stable code, identical on every machine and in every Rust version. It's what you search for and what you look up.
  2. The message, read as a sentence: no method named length found for reference &[u8]. It names both halves — your name and its type.
  3. --> src/lib.rs:2:19 — file, line, column. (Yours will differ.)
  4. The span — the source line with ^^^^^^ under the exact expression the compiler couldn't accept.
  5. help: — a concrete suggestion, sometimes a -/+ diff you can apply verbatim, sometimes only a pointer, and sometimes absent entirely. Which of this stage's six are generous and which are not is in the project's What goes wrong first notes. note: is help:'s quieter sibling: context rather than a fix.

Now one with two underlines — and this one is verbatim from the file you are about to fix, cargo check run from your db/ directory:

error[E0308]: mismatched types
  --> sqlkit/src/warmup.rs:21:18
   |
21 |     let n: u32 = bytes.len();
   |            ---   ^^^^^^^^^^^ expected `u32`, found `usize`
   |            |
   |            expected due to this
   |
help: you can convert a `usize` to a `u32` and panic if the converted value doesn't fit
   |
21 |     let n: u32 = bytes.len().try_into().unwrap();
   |                             ++++++++++++++++++++

The carets are on the expression rustc could not accept; the --- is on the thing that created the expectation — here the type annotation immediately to its left, which says expected due to this in as many words. That's the shape nearly every type error takes — the carets say what, the second underline says why — and the second underline is often nowhere near the first: a function's return type, an earlier argument, a let twenty lines up. Reading both is the difference between fixing it in five seconds and staring at the carets.

Read that help: twice, though. It compiles, and it is the wrong fix: it would make a function whose job is to describe any slice panic on a file bigger than four gigabytes. A suggestion is the compiler's guess at what you meant, and the compiler has not read your doc comment. Which is the project's rule in advance — the right fix is the one that keeps the function doing its job.

Two habits: errors print in the order rustc found them, not in line order, so the top one isn't necessarily earliest in the file; and fix one, then re-run, because a later error is often a consequence of an earlier one.

rustc --explain

Every E…. code has a page, offline, on your machine right now:

rustc --explain E0382

You get prose, a short example that triggers the error, and the same example fixed — and cargo reminds you at the bottom of a failing build. Run it for each of this stage's six codes, including the ones you fix easily: that's how a code becomes a word you recognise instead of a number you squint at.

Fixing errors uncovers errors

One behaviour surprises everyone once, so let's get it out of the way:

The error count can go up after a fix. That's progress, not regression.

rustc works in phases. It type-checks a function body, and only if that body type-checked does it go on to borrow-check it — the pass that enforces ownership and mutability. While a function holds a type error, rustc has no usable picture of that body and reports none of its ownership problems. They're still there, just not visible yet.

Measured on the file you're about to fix, rustc 1.97.1: the first run reports four errors and every one of them is type-level — E0425, E0599, E0308, E0061. Fix those four and the second run reports two, E0384 and E0382, the mutability and move errors that were in the same function all along.

The six you're about to meet

Code What rustc is telling you
E0308 mismatched types — this value's type isn't the one this position requires
E0384 cannot assign twice to an immutable variable — bindings are immutable unless you say mut
E0425 cannot find that name in this scope — a typo, or something never declared
E0599 no method of that name on this type — usually a near-miss name, or the wrong type
E0061 this function takes N arguments but M were supplied
E0382 a value used (or borrowed) after it was moved — Rust's ownership rule, showing up early

The first five are ordinary mistakes any language catches somehow. E0382 is the Rust-specific one: assigning a non-Copy value to a second binding moves it, and the first binding stops being usable. Stage 03 gives that its own lab; for now, recognise it — and know .clone() is the sledgehammer, not the answer.

The file you're about to recognise

This course builds a reader for the SQLite file format — the single-file database inside browsers, phones and most applications on your machine. It all starts with one question: is this even a SQLite file?

The format answers in the first sixteen bytes. Every valid SQLite database begins with the UTF-8 string SQLite format 3 plus a nul terminator — sixteen bytes exactly, 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 in hex. It's called the magic header string, and this is the whole of it:

pub const SQLITE_MAGIC: [u8; 16] = *b"SQLite format 3\0";

b"…" is a byte-string literal — not a &str but a &'static [u8; 16], a reference to a fixed-size array of raw bytes. The leading * dereferences it, so the constant is the array itself rather than a reference to one (byte arrays are Copy, which is what makes that legal in a const). Checking for it is a sixteen-byte comparison with one trap: a file shorter than sixteen bytes exists, and must answer "no" rather than crash. That trap is your project.

The shape of what's ahead: eleven stages grow one program from that constant to a tool that walks a b-tree and prints a table's rows. Nothing gets thrown away and nothing gets a dependency — the crate's [dependencies] is empty now and stays empty. What changes fastest isn't your Rust; it's how long it takes you to read an error and know what to do.

That's the lesson — free to read. Create a free account to build it: the brief, the grading table and the hint ladder are on the Lab tab.

Create a free account →

Free to start · no credit card · Rust for Beginners is free

The brief, the grading table and the hint ladder live here.

Create a free account to unlock this lab.

Create a free account →

Free to start · no credit card · Rust for Beginners is free

Chat with Guru about the check that is failing you — the written hint ladder on the Lab tab is free and unlimited either way.

Create a free account to unlock this lab.

Create a free account →

Free to start · no credit card · Rust for Beginners is free

Your submitted diff lands here with comments on the exact lines.

Create a free account to unlock this lab.

Create a free account →

Free to start · no credit card · Rust for Beginners is free