01 Structs: naming a bundle of values.
a struct is its fields, back to back · point at size_of::<Console>() with no header
Every program in examples/src/bin/ beside the output it printed, and every
file in examples/broken/ beside what rustc said about it. Each colored,
numbered section of code sits in the same row as the output it produced. Click an output line to light
up the println! that printed it, or click a println! to find its output.
Click a section's number to copy a link straight to that section, to paste where students will
follow it. Press i on a program to edit it and run it. On a phone the two columns do not fit, so a
Code/Output switch shows one at a time.
| Program | The one idea | The line to point at | Sections | |
|---|---|---|---|---|
| 01 | structs | a struct is its fields, back to back | size_of::<Console>() with no header | 7 |
| 02 | impl_and_self | . is a method, :: is an associated function | Stats::new() beside s.record_read() | 7 |
| 03 | derive_and_copy | Copy makes assignment stop moving | the same by-value method called twice | 6 |
| 04 | newtype | same bits, a genuinely different type | an alias accepting the swap a newtype rejects | 7 |
| 05 | const_fn | arithmetic the compiler already did | a static table with no start-up loop | 7 |
| 06 | repr_c | layout is a promise only #[repr(C)] makes | flag at offset 10, then at offset 0 | 7 |
| 07 | drop_guard | release became a closing brace | "release" printed on a path with no call | 7 |
| 08 | enums | exactly one of these, and nothing else | size_of::<Option<&u8>>() == 8 | 8 |
| 09 | match | the compiler lists what you forgot | the careful column vs. the _ column | 11 |
| 10 | option | absence with a type of its own | Ok(1) and Err(-1) side by side | 9 |
| 11 | slices | pointer + length, bounds-checked | &[u8] is 16 bytes, &[u8; 8] is 8 | 8 |
| 12 | tables_and_iterators | a fixed table, searched lazily | the closure running twice, not eight times | 12 |
| Error | What rustc says | The fix |
|---|---|---|
| E0004 | non-exhaustive patterns | add the arm, or _ and lose the check |
| E0015 | cannot call non-const function in constants | make the callee const fn |
| E0080 | evaluation of constant value failed | fix the index, or use get() |
| E0184 | the trait Copy cannot be implemented for this type; the type has a destructor | drop one of the two |
| E0308 | mismatched types | wrap it, or unwrap it visibly |
| E0382 | use of moved value | derive Copy, or take &self |
| E0507 | cannot move out of self.name which is behind a shared reference | return a borrow, or clone() |
| Space j ↓ → | next section (dims the rest) |
| Shift+Space k ↑ ← | previous section |
| 0–9 | jump to that section |
| click | pin an output line and the code that printed it (click again to unpin) |
| click a number | copy a link to that section, or to the whole example from the number beside its title |
| l | copy a link to the pinned line, else the section in focus, else this example |
| Esc | unpin, then leave section focus, and drop it from the link so a reload does not bring it back |
| n p | next or previous example |
| r | next run, for programs run more than once |
| e | open or close the explanation (broken files) or the build warnings |
| + - | larger or smaller text (remembered) |
| i | edit this program and run it |
| ⌘/Ctrl+↵ | run what you have edited |
| c | compare: what you changed in the code, and what changed in the output (press Esc first if you are typing) |
| o | on a narrow screen, switch between the code and the output column |
a struct is its fields, back to back · point at size_of::<Console>() with no header
. is a method, :: is an associated function · point at Stats::new() beside s.record_read()
Copy makes assignment stop moving · point at the same by-value method called twice
same bits, a genuinely different type · point at an alias accepting the swap a newtype rejects
arithmetic the compiler already did · point at a static table with no start-up loop
layout is a promise only #[repr(C)] makes · point at flag at offset 10, then at offset 0
release became a closing brace · point at "release" printed on a path with no call
exactly one of these, and nothing else · point at size_of::<Option<&u8>>() == 8
the compiler lists what you forgot · point at the careful column vs. the _ column
absence with a type of its own · point at Ok(1) and Err(-1) side by side
pointer + length, bounds-checked · point at &[u8] is 16 bytes, &[u8; 8] is 8
a fixed table, searched lazily · point at the closure running twice, not eight times
a variant was added; a match was not · fix: add the arm, or _ and lose the check
THE FEATURE, NOT A BUG. A fifth trap cause was added last week. The compiler now lists every match that has not been told what the new one means -- by file and line, before the kernel boots. In C every switch keeps compiling and silently takes its default arm.
_ => false compiles, and switches the check off forever: a catch-all covers variants that do not exist yet. Right for open domains such as a syscall number from a user program; wrong for a closed set you own.a non-const fn in a const context · fix: make the callee const fn
A const context -- a const or static item, an array length, an array repeat, an enum discriminant -- is a place the value must be known before the program runs. Only a const fn may be called there.
This matters in a kernel because a static must be valid when the first Rust function is entered, and at that point there is no heap, no allocator, and nothing that could have run an initialization loop.
blocks_for as const fn. Its body is already legal in one.a constant index past the end · fix: fix the index, or use get()
An index is checked, and in a const context it is checked by the compiler. The array has four elements, so index 8 is out of range and the const cannot be evaluated -- the program never gets built, let alone run.
At run time the same index is an unsigned compare and a never-taken branch, about three instructions, calling panic_bounds_check if it fails. In C the read simply happens: whatever lives 32 bytes past the array is what you get.
TABLE.get(8) returns Option, and None is a normal outcome.Copy cannot be implemented for this type; the type has a destructorCopy on a type with a destructor · fix: drop one of the two
COPY XOR DROP. Copy says "duplicating the bits duplicates the value"; Drop says "the release runs exactly once". A type that promised both would run its release once per copy -- which is the double free, in a new hat.
This is not a rule you have to remember. It is a combination the type system refuses, so the bug cannot be written.
Copy derive. A guard is meant to have exactly one owner.Drop impl, if the type really owns nothing.a raw integer where a newtype belongs · fix: wrap it, or unwrap it visibly
The payoff of the newtype pattern, and the whole reason to pay for it. A block number and a byte count are both "just a u32", and swapping them is a bug that compiles, links, and ships. Give the block number its own type and the compiler catches every one.
With type BlockNo = u32; -- an ALIAS -- both lines below compile happily.
read_block(BlockNo(7)).BlockNo(n.0) -- so it is one greppable line rather than an invisible accident.a by-value self consumed the value · fix: derive Copy, or take &self
flags takes self BY VALUE, so calling it moves the entry away and the second call has nothing left. A by-value receiver is a move unless the type is Copy -- and this one is Clone but not Copy.
Copying four bytes was always cheap. What Copy buys is that assignment stops MOVING, which is what lets a small type offer by-value methods.
#[derive(Clone, Copy)] -- the call then copies four bytes.&self in both methods -- borrowing never moves. That is the answer for anything too big or too resource-owning to copy.self.name which is behind a shared referencemoving a field out of &self · fix: return a borrow, or clone()
&self is a borrow: the caller still owns the Device and expects it back intact. Moving the String out would leave a hole in a value somebody else owns, so the compiler refuses.
Only a by-value self may take a field away -- nobody is left to notice.
fn name(&self) -> &str { &self.name }.self.name.clone() -- a second buffer, allocated visibly.self by value if the Device really is finished with.