← In Class CS 326 · Week 03 · Code and output Space/j/k sections · click to pin · n/p example · i edit · l copy link

Week 03 · Structs, Enums, and Fixed Tables

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.

The programs

ProgramThe one ideaThe line to point atSections
01structsa struct is its fields, back to backsize_of::<Console>() with no header7
02impl_and_self. is a method, :: is an associated functionStats::new() beside s.record_read()7
03derive_and_copyCopy makes assignment stop movingthe same by-value method called twice6
04newtypesame bits, a genuinely different typean alias accepting the swap a newtype rejects7
05const_fnarithmetic the compiler already dida static table with no start-up loop7
06repr_clayout is a promise only #[repr(C)] makesflag at offset 10, then at offset 07
07drop_guardrelease became a closing brace"release" printed on a path with no call7
08enumsexactly one of these, and nothing elsesize_of::<Option<&u8>>() == 88
09matchthe compiler lists what you forgotthe careful column vs. the _ column11
10optionabsence with a type of its ownOk(1) and Err(-1) side by side9
11slicespointer + length, bounds-checked&[u8] is 16 bytes, &[u8; 8] is 88
12tables_and_iteratorsa fixed table, searched lazilythe closure running twice, not eight times12

Programs that must not compile

ErrorWhat rustc saysThe fix
E0004non-exhaustive patternsadd the arm, or _ and lose the check
E0015cannot call non-const function in constantsmake the callee const fn
E0080evaluation of constant value failedfix the index, or use get()
E0184the trait Copy cannot be implemented for this type; the type has a destructordrop one of the two
E0308mismatched typeswrap it, or unwrap it visibly
E0382use of moved valuederive Copy, or take &self
E0507cannot move out of self.name which is behind a shared referencereturn a borrow, or clone()

Keys

Space j ↓ →next section (dims the rest)
Shift+Space k ↑ ←previous section
0–9jump to that section
clickpin an output line and the code that printed it (click again to unpin)
click a numbercopy a link to that section, or to the whole example from the number beside its title
lcopy a link to the pinned line, else the section in focus, else this example
Escunpin, then leave section focus, and drop it from the link so a reload does not bring it back
n pnext or previous example
rnext run, for programs run more than once
eopen or close the explanation (broken files) or the build warnings
+ -larger or smaller text (remembered)
iedit this program and run it
⌘/Ctrl+↵run what you have edited
ccompare: what you changed in the code, and what changed in the output (press Esc first if you are typing)
oon a narrow screen, switch between the code and the output column

01 Structs: naming a bundle of values.

a struct is its fields, back to back · point at size_of::<Console>() with no header

$ cargo run --bin 01_structs
//! 01 — Structs: naming a bundle of values.▸ 17 more lines
//!
//! A struct bundles several values into one named type that travels as a unit.
//! In memory it is a contiguous block of bytes and nothing else: no object
//! header, no type pointer, no vtable, no allocation. That is the only reason
//! a struct can sit at a hardware boundary.
//!
//! struct Console { base, chars_out, errors }
//!
//! +----------------+----------------+----------------+
//! | base | chars_out | errors |
//! +----------------+----------------+----------------+
//! 0 8 16 24 <- byte offsets
//!
//! A struct is a PRODUCT type: a Console is a base AND a count AND a count.
//! Program 08 is the other half of that pair.
//!
//! Run: cargo run --bin 01_structs

use std::mem::size_of;

fn main() {
1 println!("== building one ==");

// A struct literal names the type and every field.
let uart = Console { base: 0x1000_0000, chars_out: 0, errors: 0 };
println!("uart.base = {:#x}", uart.base);
println!("uart.chars_out = {}", uart.chars_out);

1== building one ==
uart.base = 0x10000000
uart.chars_out = 0
2 println!("\n== field init shorthand ==");
// When a local already has the field's name, write it once.
let base = 0x1001_0000;
let second = Console { base, chars_out: 7, errors: 0 };
println!("Console {{ base }} means Console {{ base: base }} -> {:#x}", second.base);

2== field init shorthand ==
Console { base } means Console { base: base } -> 0x10010000
3 println!("\n== a struct is its fields, back to back ==");
println!("size_of::<Console>() = {}", size_of::<Console>());
println!("3 x size_of::<usize>() = {}", 3 * size_of::<usize>());
println!("no header, no vtable, no allocation -- the value IS the bytes");

3== a struct is its fields, back to back ==
size_of::<Console>() = 24
3 x size_of::<usize>() = 24
no header, no vtable, no allocation -- the value IS the bytes
4 println!("\n== which is why an array of them is exactly N times as big ==");
println!("size_of::<[Console; 4]>() = {}", size_of::<[Console; 4]>());
let table = [Console { base: 0, chars_out: 0, errors: 0 }; 4];
println!("first element at {:p}", &table[0]);
println!("next element at {:p} <- {} bytes further on",
&table[1], size_of::<Console>());

4== which is why an array of them is exactly N times as big ==
size_of::<[Console; 4]>() = 96
first element at 0x16b8a5f10
next element at 0x16b8a5f28 <- 24 bytes further on
5 println!("\n== structs nest by value ==");
// `stats` is stored INSIDE the driver, not behind a pointer to it.
let driver = Driver {
console: Console { base: 0x1000_0000, chars_out: 3, errors: 0 },
stats: Stats { reads: 12, writes: 4 },
};
println!("size_of::<Driver>() = {} (= Console {} + Stats {})",
size_of::<Driver>(), size_of::<Console>(), size_of::<Stats>());
println!("driver at {:p}", &driver);
println!("driver.console at {:p}", &driver.console);
println!("driver.stats at {:p}", &driver.stats);
println!("both sit INSIDE the driver's own {} bytes -- no pointer, no",
size_of::<Driver>());
println!("second allocation, nothing to dereference");
println!("which one comes first is the compiler's choice (program 06)");
println!("reads = {}, writes = {}", driver.stats.reads, driver.stats.writes);

5== structs nest by value ==
size_of::<Driver>() = 40 (= Console 24 + Stats 16)
driver at 0x16b8a6008
driver.console at 0x16b8a6018
driver.stats at 0x16b8a6008
both sit INSIDE the driver's own 40 bytes -- no pointer, no
second allocation, nothing to dereference
which one comes first is the compiler's choice (program 06)
reads = 12, writes = 4
6 println!("\n== mutation needs `mut` on the binding ==");
let mut counter = Stats { reads: 0, writes: 0 };
counter.reads += 1;
counter.writes += 2;
println!("after two updates: {:?}", counter);
// let fixed = Stats { reads: 0, writes: 0 };
// fixed.reads += 1; // error[E0594]: cannot assign to `fixed.reads`

6== mutation needs `mut` on the binding ==
after two updates: Stats { reads: 1, writes: 2 }
7 println!("\n== private fields are how a module protects itself ==");
let log = ErrorLog::new();
println!("count = {}", log.count());
// println!("{}", log.count_field); // error[E0616]: field is private
println!("`count_field` is private, so the only way in is a method");
println!("that is how a kernel module stops the rest of the kernel");
println!("from corrupting its data behind its back");
7== private fields are how a module protects itself ==
count = 0
`count_field` is private, so the only way in is a method
that is how a kernel module stops the rest of the kernel
from corrupting its data behind its back
}

/// A memory-mapped serial port, plus what we have sent through it.
///
/// `pub` on the struct exports the type; `pub` on a field exports access to
/// that field. Leaving it off makes the field private to this file.
#[derive(Debug, Clone, Copy)]
pub struct Console {
pub base: usize,
pub chars_out: usize,
pub errors: usize,
}

/// Two counters. Program 02 gives this one behavior.
#[derive(Debug, Clone, Copy)]
pub struct Stats {
pub reads: usize,
pub writes: usize,
}

/// A struct whose fields are themselves structs, stored by value.
pub struct Driver {
pub console: Console,
pub stats: Stats,
}

/// A struct with nothing public about it at all.
pub struct ErrorLog {
count_field: usize,
}

impl ErrorLog {
pub fn new() -> ErrorLog {
ErrorLog { count_field: 0 }
}

pub fn count(&self) -> usize {
self.count_field
}
}
exit status 0

02 impl: giving a type behavior, and choosing the receiver.

. is a method, :: is an associated function · point at Stats::new() beside s.record_read()

$ cargo run --bin 02_impl_and_self
//! 02 — impl: giving a type behavior, and choosing the receiver.▸ 16 more lines
//!
//! A function whose first parameter is some form of `self` is a METHOD, called
//! with a dot. One with no `self` is an ASSOCIATED FUNCTION, called through the
//! type with `::`. Rust has no `constructor` keyword; `new` is a convention.
//!
//! s.record_read() method -> fn record_read(&mut self)
//! Stats::new() associated fn -> fn new() -> Self
//!
//! Choosing between &self, &mut self and self is the ownership decision from
//! last week, applied to methods:
//!
//! &self shared borrow caller keeps it reading
//! &mut self unique borrow caller keeps it mutating
//! self by value caller loses it consuming (or Copy)
//!
//! Run: cargo run --bin 02_impl_and_self

fn main() {
1 println!("== associated function: `::`, because there is no value yet ==");
let mut s = Stats::new();
println!("Stats::new() -> {s:?}");
println!("Stats::started(9) -> {:?}", Stats::started(9));

1== associated function: `::`, because there is no value yet ==
Stats::new() -> Stats { reads: 0, writes: 0 }
Stats::started(9) -> Stats { reads: 9, writes: 0 }
2 println!("\n== &self reads; the caller keeps the value ==");
println!("s.total() = {}", s.total());
println!("s.total() = {} <- called twice, still ours", s.total());
println!("s.is_idle() = {}", s.is_idle());

2== &self reads; the caller keeps the value ==
s.total() = 0
s.total() = 0 <- called twice, still ours
s.is_idle() = true
3 println!("\n== &mut self mutates, under an exclusive borrow ==");
s.record_read();
s.record_read();
s.record_write();
println!("after 2 reads and 1 write: {s:?}, total {}", s.total());
println!("while `record_read` runs, no other reference to `s` exists");

3== &mut self mutates, under an exclusive borrow ==
after 2 reads and 1 write: Stats { reads: 2, writes: 1 }, total 3
while `record_read` runs, no other reference to `s` exists
4 println!("\n== self by value consumes -- unless the type is Copy ==");
// Stats derives Copy, so this call copies 16 bytes and `s` survives.
let doubled = s.scaled(2);
println!("s.scaled(2) = {doubled:?}");
println!("s is still usable: {s:?} <- only because Stats is Copy");

4== self by value consumes -- unless the type is Copy ==
s.scaled(2) = Stats { reads: 4, writes: 2 }
s is still usable: Stats { reads: 2, writes: 1 } <- only because Stats is Copy
5 println!("\n== ... and consuming is sometimes exactly what you want ==");
let report = s.into_report();
println!("s.into_report() -> {report:?}");
println!("`into_report` takes self to say: this Stats is finished with");

5== ... and consuming is sometimes exactly what you want ==
s.into_report() -> (2, 1, 3)
`into_report` takes self to say: this Stats is finished with
6 println!("\n== a type that is NOT Copy: self really does take it away ==");
let dev = Device::new("uart0");
println!("dev.name_len() = {} (&self: borrows)", dev.name_len());
println!("dev.name_len() = {} (still ours)", dev.name_len());
let name = dev.into_name();
println!("dev.into_name() -> {name:?}");
// println!("{}", dev.name_len()); // error[E0382]: borrow of moved value

6== a type that is NOT Copy: self really does take it away ==
dev.name_len() = 5 (&self: borrows)
dev.name_len() = 5 (still ours)
dev.into_name() -> "uart0"
7 println!("\n== Self is the type's own name, inside its impl block ==");
println!("`-> Self` and `-> Stats` mean the same thing in there");
println!("chained: {:?}", Stats::new().plus_read().plus_read().plus_write());
7== Self is the type's own name, inside its impl block ==
`-> Self` and `-> Stats` mean the same thing in there
chained: Stats { reads: 2, writes: 1 }
}

/// Two counters, and every shape of receiver.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Stats {
pub reads: usize,
pub writes: usize,
}

impl Stats {
/// Associated function: no `self`, called as `Stats::new()`.
///
/// There is no value to call a method on yet, because making one is the
/// job. Every constructor in a kernel has this shape.
pub fn new() -> Self {
Stats { reads: 0, writes: 0 }
}

/// Another one. `new` is a convention, not a keyword -- name it for what
/// it makes.
pub fn started(reads: usize) -> Self {
Stats { reads, writes: 0 }
}

/// `&self`: reads two fields, changes nothing, caller keeps the value.
pub fn total(&self) -> usize {
self.reads + self.writes
}

/// `&self` again, and a method may call another method on the same value.
pub fn is_idle(&self) -> bool {
self.total() == 0
}

/// `&mut self`: while this runs, no other reference to the value exists.
pub fn record_read(&mut self) {
self.reads += 1;
}

pub fn record_write(&mut self) {
self.writes += 1;
}

/// `self` by value on a small `Copy` type: the call copies 16 bytes and
/// the caller's value is untouched.
pub fn scaled(self, factor: usize) -> Self {
Stats { reads: self.reads * factor, writes: self.writes * factor }
}

/// `self` by value to CONSUME: after this the Stats is finished with, and
/// the name says so. (`into_` is the convention for that.)
pub fn into_report(self) -> (usize, usize, usize) {
(self.reads, self.writes, self.total())
}

/// Taking and returning `Self` is what makes calls chain.
pub fn plus_read(mut self) -> Self {
self.reads += 1;
self
}

pub fn plus_write(mut self) -> Self {
self.writes += 1;
self
}
}

/// A type that owns a heap buffer, so it can never be `Copy`.
pub struct Device {
name: String,
}

impl Device {
pub fn new(name: &str) -> Self {
Device { name: name.to_string() }
}

/// Borrows: call it as often as you like.
pub fn name_len(&self) -> usize {
self.name.len()
}

/// Consumes: the Device is gone, and the String comes out of it.
pub fn into_name(self) -> String {
self.name
}
}
exit status 0

03 derive: four traits the compiler will write for you.

Copy makes assignment stop moving · point at the same by-value method called twice

$ cargo run --bin 03_derive_and_copy
//! 03 — derive: four traits the compiler will write for you.▸ 16 more lines
//!
//! `#[derive(...)]` asks rustc for the mechanical implementations:
//!
//! Debug {:?} printing -- and what assert_eq! shows on failure
//! Clone an explicit .clone()
//! Copy assignment DUPLICATES instead of moving
//! PartialEq == and != , field by field
//!
//! `Copy` is the load-bearing one, and it is not about speed: copying eight
//! bytes was always cheap. It makes assignment stop MOVING, which is what lets
//! a by-value `self` method be called twice on the same value.
//!
//! Copy XOR Drop. A destructor means one release point; a copy means many
//! values. Allow both and the release runs once per copy -- a double free.
//!
//! Run: cargo run --bin 03_derive_and_copy

fn main() {
1 println!("== Debug: what {{:?}} and assert_eq! print ==");
let t = Ticks { count: 42, hz: 10_000_000 };
println!("{{:?}} -> {t:?}");
println!("{{:#?}} ->");
println!("{t:#?}");

1== Debug: what {:?} and assert_eq! print ==
{:?} -> Ticks { count: 42, hz: 10000000 }
{:#?} ->
Ticks {
count: 42,
hz: 10000000,
}
2 println!("\n== PartialEq: == field by field ==");
let a = Ticks { count: 42, hz: 10_000_000 };
let b = Ticks { count: 43, hz: 10_000_000 };
println!("t == a -> {}", t == a);
println!("t == b -> {}", t == b);
println!("nothing was written by hand; the derive compares every field");

2== PartialEq: == field by field ==
t == a -> true
t == b -> false
nothing was written by hand; the derive compares every field
3 println!("\n== Copy: assignment duplicates ==");
let mut first = Ticks { count: 1, hz: 100 };
let second = first; // a copy, not a move
first.count = 999; // touching one does not touch the other
println!("first = {first:?} at {:p}", &first);
println!("second = {second:?} at {:p} <- an independent value", &second);

3== Copy: assignment duplicates ==
first = Ticks { count: 999, hz: 100 } at 0x16b6b1fa8
second = Ticks { count: 1, hz: 100 } at 0x16b6b1fb8 <- an independent value
4 println!("\n== which is what makes a by-value method callable twice ==");
let ms = Ticks { count: 30_000_000, hz: 10_000_000 };
println!("ms.seconds() = {}", ms.seconds());
println!("ms.half() = {:?}", ms.half());
println!("ms.seconds() = {} <- `seconds(self)` copied, it did not move",
ms.seconds());
println!("drop the Copy derive and the second call is error[E0382]");

4== which is what makes a by-value method callable twice ==
ms.seconds() = 3
ms.half() = Ticks { count: 15000000, hz: 10000000 }
ms.seconds() = 3 <- `seconds(self)` copied, it did not move
drop the Copy derive and the second call is error[E0382]
5 println!("\n== a type that owns something moves instead ==");
let n1 = Named { label: String::from("timer") };
let n2 = n1.clone(); // an explicit, visible allocation
println!("n1.label at {:p}", n1.label.as_ptr());
println!("n2.label at {:p} <- clone() allocated a second buffer",
n2.label.as_ptr());
let moved = n1; // a move: Named is Clone but not Copy
println!("moved = {moved:?}");
// println!("{n1:?}"); // error[E0382]: borrow of moved value

5== a type that owns something moves instead ==
n1.label at 0x104fd5cf0
n2.label at 0x104fd5d00 <- clone() allocated a second buffer
moved = Named { label: "timer" }
6 println!("\n== Copy XOR Drop ==");
{
let _guard = Held { id: 7 };
println!("Held {{ id: 7 }} created");
} // <- its Drop runs here
println!("Held has a destructor, so it can never derive Copy:");
println!(" error[E0184]: the trait `Copy` cannot be implemented for this");
println!(" type; the type has a destructor");
println!("try it -- add #[derive(Clone, Copy)] to `Held` and rebuild");
println!("\n**RUN** ../examples/show-errors.sh e0184");
6== Copy XOR Drop ==
Held { id: 7 } created
drop(Held { id: 7 }) <- released at the closing brace
Held has a destructor, so it can never derive Copy:
error[E0184]: the trait `Copy` cannot be implemented for this
type; the type has a destructor
try it -- add #[derive(Clone, Copy)] to `Held` and rebuild

**RUN** ../examples/show-errors.sh e0184
}

/// Plain data: two integers and no resource, so `Copy` is the right choice.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Ticks {
pub count: u64,
pub hz: u64,
}

impl Ticks {
/// Takes `self` by value. Legal to call twice only because Ticks is Copy.
pub fn seconds(self) -> u64 {
self.count / self.hz
}

pub fn half(self) -> Ticks {
Ticks { count: self.count / 2, hz: self.hz }
}
}

/// Owns a heap buffer, so `Clone` yes, `Copy` never.
#[derive(Debug, Clone)]
pub struct Named {
pub label: String,
}

/// Has a destructor, so `Copy` is rejected outright.
pub struct Held {
pub id: usize,
}

impl Drop for Held {
fn drop(&mut self) {
println!(" drop(Held {{ id: {} }}) <- released at the closing brace", self.id);
}
}
exit status 0

04 The newtype pattern: same bits, a different type.

same bits, a genuinely different type · point at an alias accepting the swap a newtype rejects

$ cargo run --bin 04_newtype
//! 04 — The newtype pattern: same bits, a different type.▸ 15 more lines
//!
//! A kernel written only in integers cannot be checked. A block number, a byte
//! count, a device number and a permission word are all "just a number", and
//! the compiler cannot tell them apart while they share a type.
//!
//! type BlockNo = u32; an ALIAS. Changes nothing rustc checks.
//! struct BlockNo(u32); a NEWTYPE. A different type, the same bits.
//!
//! A struct with unnamed fields is a TUPLE STRUCT; one with a single field is a
//! newtype. You reach the value by position: `self.0`.
//!
//! #[repr(transparent)] promises the wrapper has the EXACT layout and ABI
//! of the field inside it. Everything you buy is at compile time.
//!
//! Run: cargo run --bin 04_newtype

use std::mem::size_of;

fn main() {
1 println!("== an alias buys nothing ==");
let n: RawBlock = 7; // RawBlock IS u32, interchangeably
let bytes: RawCount = n; // so this is allowed, and it is a bug
println!("type RawBlock = u32; type RawCount = u32;");
println!("a block number assigned straight into a byte count: {bytes}");
println!("rustc has no opinion, because they are the same type");

1== an alias buys nothing ==
type RawBlock = u32; type RawCount = u32;
a block number assigned straight into a byte count: 7
rustc has no opinion, because they are the same type
2 println!("\n== a newtype does ==");
let block = BlockNo(7);
let count = ByteCount(512);
println!("BlockNo(7) -> {block:?}");
println!("ByteCount(512) -> {count:?}");
println!("read_block(block) is fine; read_block(count) is E0308");
// read_block(count); // error[E0308]: mismatched types
// read_block(7); // error[E0308]: expected `BlockNo`, found integer
read_block(block);

2== a newtype does ==
BlockNo(7) -> BlockNo(7)
ByteCount(512) -> ByteCount(512)
read_block(block) is fine; read_block(count) is E0308
read_block(BlockNo(7)) -> reading disk block 7
3 println!("\n== and costs nothing at run time ==");
println!("size_of::<u32>() = {}", size_of::<u32>());
println!("size_of::<BlockNo>() = {} <- repr(transparent)", size_of::<BlockNo>());
println!("size_of::<[BlockNo; 8]>() = {}", size_of::<[BlockNo; 8]>());
println!("same size, same alignment, same register, same instructions");

3== and costs nothing at run time ==
size_of::<u32>() = 4
size_of::<BlockNo>() = 4 <- repr(transparent)
size_of::<[BlockNo; 8]>() = 32
same size, same alignment, same register, same instructions
4 println!("\n== reach the value by position: self.0 ==");
println!("block.0 = {}", block.0);
println!("block.next() = {:?}", block.next());
println!("count.blocks() = {} (512-byte blocks)", count.blocks());

4== reach the value by position: self.0 ==
block.0 = 7
block.next() = BlockNo(8)
count.blocks() = 1 (512-byte blocks)
5 println!("\n== a newtype over packed bits ==");
// A Unix mode word: three 3-bit permission fields side by side.
//
// bits [8..6] owner bits [5..3] group bits [2..0] other
// within each field: 4 = read, 2 = write, 1 = execute
let mode = Mode::new(0b111, 0b101, 0b101); // rwxr-xr-x
println!("Mode::new(0o7, 0o5, 0o5) = {:#o} ({:#011b})", mode.0, mode.0);
println!("mode.owner() = {:#o}", mode.owner());
println!("mode.group() = {:#o}", mode.group());
println!("mode.other() = {:#o}", mode.other());
println!("mode.owner_can_write() = {}", mode.owner_can_write());

5== a newtype over packed bits ==
Mode::new(0o7, 0o5, 0o5) = 0o755 (0b111101101)
mode.owner() = 0o7
mode.group() = 0o5
mode.other() = 0o5
mode.owner_can_write() = true
6 println!("\n== packing is a shift and an OR; unpacking is a shift and a mask ==");
println!("build : (owner << 6) | (group << 3) | other");
println!("read : (self.0 >> 6) & 0o7");
let ro = Mode::new(0b100, 0b100, 0b100); // r--r--r--
println!("read-only {:#o}: owner_can_write() = {}", ro.0, ro.owner_can_write());

6== packing is a shift and an OR; unpacking is a shift and a mask ==
build : (owner << 6) | (group << 3) | other
read : (self.0 >> 6) & 0o7
read-only 0o444: owner_can_write() = false
7 println!("\n== the deliberate reinterpretation is spelled out ==");
let raw: u32 = block.0; // one explicit unwrap, greppable
let back = BlockNo(raw); // one explicit wrap, greppable
println!("BlockNo -> u32 -> BlockNo: {back:?}");
println!("the conversions still exist; now they are visible");
println!("\n**RUN** ../examples/show-errors.sh e0308");
7== the deliberate reinterpretation is spelled out ==
BlockNo -> u32 -> BlockNo: BlockNo(7)
the conversions still exist; now they are visible

**RUN** ../examples/show-errors.sh e0308
}

/// A type ALIAS. `RawBlock` and `u32` are interchangeable everywhere.
pub type RawBlock = u32;
/// Another one, and nothing stops you assigning one to the other.
pub type RawCount = u32;

/// A NEWTYPE: a distinct type, the same 32 bits.
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BlockNo(pub u32);

/// A second newtype over the same representation, and not the same type.
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ByteCount(pub u32);

/// A Unix permission word, three 3-bit fields packed into one integer.
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Mode(pub u16);

/// One 3-bit field: rwx.
pub const PERM_MASK: u16 = 0o7;

impl BlockNo {
pub const fn next(self) -> BlockNo {
BlockNo(self.0 + 1)
}
}

impl ByteCount {
/// How many 512-byte blocks these bytes occupy, rounded up.
pub const fn blocks(self) -> u32 {
(self.0 + 511) / 512
}
}

impl Mode {
/// Pack three permission fields into one word.
pub const fn new(owner: u16, group: u16, other: u16) -> Mode {
Mode((owner << 6) | (group << 3) | other)
}

pub const fn owner(self) -> u16 {
(self.0 >> 6) & PERM_MASK
}

pub const fn group(self) -> u16 {
(self.0 >> 3) & PERM_MASK
}

pub const fn other(self) -> u16 {
self.0 & PERM_MASK
}

/// Testing one bit is always this shape: AND with it, compare to zero.
pub const fn owner_can_write(self) -> bool {
self.owner() & 0b010 != 0
}
}

/// Takes a block number and nothing else. A byte count will not compile.
fn read_block(b: BlockNo) {
println!("read_block({b:?}) -> reading disk block {}", b.0);
}
exit status 0

05 const fn: arithmetic the compiler does for you.

arithmetic the compiler already did · point at a static table with no start-up loop

$ cargo run --bin 05_const_fn
//! 05 — const fn: arithmetic the compiler does for you.▸ 24 more lines
//!
//! A `const fn` may ALSO be evaluated by the compiler, before the program runs.
//! `const` only ADDS an ability -- the same function is callable at run time.
//!
//! const CAP: usize = kib(4); const-evaluated, baked into the image
//! let n = kib(runtime_value); ordinary call, ordinary instructions
//!
//! A CONST CONTEXT is any place the value must be known before the program
//! runs: a `const` or `static` item, an array length, an array repeat, an enum
//! discriminant.
//!
//! +---------------------+ yes +------------------+ +-------------------+
//! | const / static / |------>| const-eval in |->| a literal in the |
//! | array length? | | the compiler | | executable image |
//! +---------------------+ +------------------+ +-------------------+
//! | no
//! v
//! code generation -> shifts and adds at run time
//!
//! Why a kernel needs this: a static table 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.
//!
//! Run: cargo run --bin 05_const_fn

use std::mem::size_of;

fn main() {
1 println!("== the same function, both ways ==");
println!("KERNEL_STACK = kib(8) = {KERNEL_STACK} <- computed by rustc");
let pages = read_a_number();
println!("kib({pages}) = {} <- computed at run time", kib(pages));
println!("`const fn` does not mean `only at compile time`");

1== the same function, both ways ==
KERNEL_STACK = kib(8) = 8192 <- computed by rustc
kib(4) = 4096 <- computed at run time
`const fn` does not mean `only at compile time`
2 println!("\n== a const context forces the evaluation ==");
const BLOCKS: u32 = blocks_for(5000);
println!("const BLOCKS: u32 = blocks_for(5000) = {BLOCKS}");
println!("no divide instruction exists anywhere in this binary for it");
println!("blocks_for(5000) at run time = {}", blocks_for(read_bytes()));

2== a const context forces the evaluation ==
const BLOCKS: u32 = blocks_for(5000) = 10
no divide instruction exists anywhere in this binary for it
blocks_for(5000) at run time = 10
3 println!("\n== an array length is a const context ==");
let buf = [0u8; BUF_LEN];
println!("const BUF_LEN: usize = kib(1) / 8 = {BUF_LEN}");
println!("[0u8; BUF_LEN] is {} bytes on the stack", buf.len());
println!("size_of::<[u8; BUF_LEN]>() = {}", size_of::<[u8; BUF_LEN]>());

3== an array length is a const context ==
const BUF_LEN: usize = kib(1) / 8 = 128
[0u8; BUF_LEN] is 128 bytes on the stack
size_of::<[u8; BUF_LEN]>() = 128
4 println!("\n== a whole table, initialized with no code running ==");
println!("static SLOTS: [Slot; {NSLOT}] = [const {{ Slot::new() }}; {NSLOT}];");
println!("SLOTS[0] = {:?}", SLOTS[0]);
println!("SLOTS[7] = {:?}", SLOTS[7]);
println!("all {} of them were written into the executable by the compiler",
SLOTS.len());
println!("size_of::<[Slot; {NSLOT}]>() = {} bytes reserved at link time",
size_of::<[Slot; NSLOT]>());

4== a whole table, initialized with no code running ==
static SLOTS: [Slot; 8] = [const { Slot::new() }; 8];
SLOTS[0] = Slot { owner: 0, busy: false }
SLOTS[7] = Slot { owner: 0, busy: false }
all 8 of them were written into the executable by the compiler
size_of::<[Slot; 8]>() = 64 bytes reserved at link time
5 println!("\n== ... and a const one, built the same way ==");
println!("const READY: Slot = Slot::started(3);");
println!("READY = {READY:?} <- a literal in the image, not a call");
println!("READY.is_busy() = {}", READY.is_busy());

5== ... and a const one, built the same way ==
const READY: Slot = Slot::started(3);
READY = Slot { owner: 3, busy: true } <- a literal in the image, not a call
READY.is_busy() = true
6 println!("\n== what C had to do instead ==");
println!("C static initializers allow constants only: `= 0` yes, a call no.");
println!("Hence the pile of xxx_init() routines whose whole job is to fill");
println!("in what the language could not. C++ answered with constexpr in");
println!("2011; Rust's const fn stabilized in 2018.");

6== what C had to do instead ==
C static initializers allow constants only: `= 0` yes, a call no.
Hence the pile of xxx_init() routines whose whole job is to fill
in what the language could not. C++ answered with constexpr in
2011; Rust's const fn stabilized in 2018.
7 println!("\n== what you may write inside a const fn ==");
println!(" arithmetic, comparisons, if, match, loop, other const fns");
println!(" NOT: allocate, call a non-const fn, deref a raw pointer");
println!("try it: put `read_a_number()` in a const and read E0015");
println!("\n**RUN** ../examples/show-errors.sh e0015");
7== what you may write inside a const fn ==
arithmetic, comparisons, if, match, loop, other const fns
NOT: allocate, call a non-const fn, deref a raw pointer
try it: put `read_a_number()` in a const and read E0015

**RUN** ../examples/show-errors.sh e0015
}

/// Bytes in `n` kibibytes.
pub const fn kib(n: usize) -> usize {
n * 1024
}

/// How many 512-byte blocks `bytes` occupies, rounded up.
///
/// `if` and `match` are both allowed inside a `const fn`.
pub const fn blocks_for(bytes: u32) -> u32 {
if bytes == 0 {
0
} else {
(bytes + 511) / 512
}
}

/// A const context: this is evaluated by rustc, not at start-up.
pub const KERNEL_STACK: usize = kib(8);

/// So is this, and it is then used as an array length below.
pub const BUF_LEN: usize = kib(1) / 8;

/// How many slots the table has.
pub const NSLOT: usize = 8;

/// One entry of a fixed table. Not `Copy`, on purpose -- see `SLOTS`.
#[derive(Debug)]
pub struct Slot {
pub owner: u32,
pub busy: bool,
}

impl Slot {
/// The compiler can run this, so a `static` array of them needs no loop.
pub const fn new() -> Slot {
Slot { owner: 0, busy: false }
}

pub const fn started(owner: u32) -> Slot {
Slot { owner, busy: true }
}

pub const fn is_busy(&self) -> bool {
self.busy
}
}

/// Eight fully initialized slots, in the executable, before anything runs.
///
/// The `[const { ... }; N]` form is needed because the plain repeat form
/// requires the element type to be `Copy`, and `Slot` is not.
pub static SLOTS: [Slot; NSLOT] = [const { Slot::new() }; NSLOT];

/// A const item built by a `const fn` call.
pub const READY: Slot = Slot::started(3);

/// An ordinary function -- deliberately NOT const, to make the point.
fn read_a_number() -> usize {
4
}

fn read_bytes() -> u32 {
5000
}
exit status 0

06 repr(C): when something other than Rust reads your struct.

layout is a promise only #[repr(C)] makes · point at flag at offset 10, then at offset 0

$ cargo run --bin 06_repr_c
//! 06 — repr(C): when something other than Rust reads your struct.▸ 22 more lines
//!
//! By default a struct has representation `repr(Rust)`, and the language
//! promises NOTHING about layout: not field order, not padding, not that two
//! identical declarations agree. The compiler may sort fields to reduce
//! padding, and it does.
//!
//! #[repr(C)] gives that freedom up and uses C's rules: fields in source
//! order, each at the next offset satisfying its alignment.
//!
//! You need it exactly when something that is not the Rust compiler reads the
//! bytes -- assembly, hardware, a C library, or a saved disk image.
//!
//! repr(C) Header the assembly that indexes it
//! +----------------------+
//! 0 | flag u8 | sb a1, 0(a0)
//! | (7 bytes of padding) |
//! 8 | addr u64 | sd a2, 8(a0)
//! 16 | count u16 | sh a3, 16(a0)
//! | (6 bytes of padding) |
//! +----------------------+ 24 bytes
//!
//! Run: cargo run --bin 06_repr_c

use std::mem::{align_of, offset_of, size_of};

fn main() {
1 println!("== the same three fields, two representations ==");
println!("size_of::<RustHeader>() = {:>2} align {}",
size_of::<RustHeader>(), align_of::<RustHeader>());
println!("size_of::<CHeader>() = {:>2} align {}",
size_of::<CHeader>(), align_of::<CHeader>());
println!("declared identically; only the attribute differs");

1== the same three fields, two representations ==
size_of::<RustHeader>() = 16 align 8
size_of::<CHeader>() = 24 align 8
declared identically; only the attribute differs
2 println!("\n== where the fields actually landed ==");
println!(" repr(Rust) #[repr(C)]");
println!(" flag offset {:>2} offset {:>2}",
offset_of!(RustHeader, flag), offset_of!(CHeader, flag));
println!(" addr offset {:>2} offset {:>2}",
offset_of!(RustHeader, addr), offset_of!(CHeader, addr));
println!(" count offset {:>2} offset {:>2}",
offset_of!(RustHeader, count), offset_of!(CHeader, count));
println!("the left column is today's answer on this compiler, and a");
println!("promise about nothing; the right column is a contract");

2== where the fields actually landed ==
repr(Rust) #[repr(C)]
flag offset 10 offset 0
addr offset 0 offset 8
count offset 8 offset 16
the left column is today's answer on this compiler, and a
promise about nothing; the right column is a contract
3 println!("\n== why the C rules cost bytes ==");
println!("`addr` is 8-aligned, so it cannot start at offset 1:");
println!(" flag at 0, then 7 bytes of padding, then addr at 8,");
println!(" count at 16, then 6 more bytes so the whole struct is 8-aligned");
println!("repr(Rust) is free to put the most-aligned field first instead");

3== why the C rules cost bytes ==
`addr` is 8-aligned, so it cannot start at offset 1:
flag at 0, then 7 bytes of padding, then addr at 8,
count at 16, then 6 more bytes so the whole struct is 8-aligned
repr(Rust) is free to put the most-aligned field first instead
4 println!("\n== order the fields yourself and the padding goes away ==");
println!("size_of::<PackedWell>() = {} (#[repr(C)], big fields first)",
size_of::<PackedWell>());
println!(" addr offset {}", offset_of!(PackedWell, addr));
println!(" count offset {}", offset_of!(PackedWell, count));
println!(" flag offset {}", offset_of!(PackedWell, flag));

4== order the fields yourself and the padding goes away ==
size_of::<PackedWell>() = 16 (#[repr(C)], big fields first)
addr offset 0
count offset 8
flag offset 10
5 println!("\n== a struct assembly reads by numeric offset ==");
let ctx = SavedRegs { ra: 0x8000_1234, sp: 0x8010_0000, s0: 0, s1: 0 };
println!("size_of::<SavedRegs>() = {} = 4 x 8", size_of::<SavedRegs>());
println!(" ra offset {} <- `ld ra, 0(a1)`", offset_of!(SavedRegs, ra));
println!(" sp offset {} <- `ld sp, 8(a1)`", offset_of!(SavedRegs, sp));
println!(" s0 offset {}", offset_of!(SavedRegs, s0));
println!(" s1 offset {}", offset_of!(SavedRegs, s1));
println!("ra = {:#x}, sp = {:#x}", ctx.ra, ctx.sp);

5== a struct assembly reads by numeric offset ==
size_of::<SavedRegs>() = 32 = 4 x 8
ra offset 0 <- `ld ra, 0(a1)`
sp offset 8 <- `ld sp, 8(a1)`
s0 offset 16
s1 offset 24
ra = 0x80001234, sp = 0x80100000
6 println!("\n== this failure is silent at compile time ==");
println!("Delete #[repr(C)] from SavedRegs and NOTHING breaks in the build.");
println!("The assembly is still valid; it may simply store the return");
println!("address into whatever field now sits at offset 0. The first");
println!("symptom is a jump to a garbage address on the next switch.");
println!("Worse: all four fields are u64, so today's compiler has no");
println!("reason to reorder them and it would probably keep working.");
println!("The bug is not that it breaks -- it is that nothing PROMISES");
println!("it will not.");

6== this failure is silent at compile time ==
Delete #[repr(C)] from SavedRegs and NOTHING breaks in the build.
The assembly is still valid; it may simply store the return
address into whatever field now sits at offset 0. The first
symptom is a jump to a garbage address on the next switch.
Worse: all four fields are u64, so today's compiler has no
reason to reorder them and it would probably keep working.
The bug is not that it breaks -- it is that nothing PROMISES
it will not.
7 println!("\n== the three you will meet ==");
println!(" repr(Rust) none; optimize freely everything internal");
println!(" #[repr(C)] source order, C alignment assembly, hardware, disk");
println!(" #[repr(transparent)] identical to the one field newtypes (program 04)");
println!("size_of::<Wrapped>() = {} == size_of::<u64>() = {}",
size_of::<Wrapped>(), size_of::<u64>());
7== the three you will meet ==
repr(Rust) none; optimize freely everything internal
#[repr(C)] source order, C alignment assembly, hardware, disk
#[repr(transparent)] identical to the one field newtypes (program 04)
size_of::<Wrapped>() = 8 == size_of::<u64>() = 8
}

/// Rust may lay these out in any order it likes.
pub struct RustHeader {
pub flag: u8,
pub addr: u64,
pub count: u16,
}

/// Exactly the same fields, in source order, with C's padding.
#[repr(C)]
pub struct CHeader {
pub flag: u8,
pub addr: u64,
pub count: u16,
}

/// `#[repr(C)]` fixes the order -- so pick a good one.
#[repr(C)]
pub struct PackedWell {
pub addr: u64,
pub count: u16,
pub flag: u8,
}

/// The registers a context switch saves. Assembly reads these by byte offset.
#[repr(C)]
#[derive(Clone, Copy)]
pub struct SavedRegs {
pub ra: u64, // return address -- offset 0
pub sp: u64, // stack pointer -- offset 8
pub s0: u64, // saved register -- offset 16
pub s1: u64, // saved register -- offset 24
}

/// The third representation: a newtype with the exact layout of its field.
#[repr(transparent)]
pub struct Wrapped(pub u64);
exit status 0

07 Drop and the guard: release became a closing brace.

release became a closing brace · point at "release" printed on a path with no call

$ cargo run --bin 07_drop_guard
//! 07 — Drop and the guard: release became a closing brace.▸ 18 more lines
//!
//! When a value goes out of scope the compiler emits a call to its type's
//! `Drop`. You do not write the call, you cannot forget it, and its site is a
//! closing brace -- including paths you did not think about: an early `return`,
//! a `?`, the end of a function the value was passed into.
//!
//! A GUARD is a value whose existence IS the held resource:
//!
//! let g = lock.acquire(); <- held from here
//! ... nothing else can reach the lock
//! } <- released here, by the compiler
//!
//! Two properties earn this a place in a kernel. It is DETERMINISTIC, so a drop
//! can carry real work; and it runs WHEREVER THE OWNER ENDS. C++ has had this
//! since the mid-1980s as RAII. What Rust adds is that ownership is checked, so
//! RAII cannot be defeated by an accidental copy or a second owner.
//!
//! Run: cargo run --bin 07_drop_guard

fn main() {
1 println!("== drop runs at the closing brace ==");
{
let _a = Tracked::new("first");
let _b = Tracked::new("second");
println!(" both alive");
}
println!("dropped in REVERSE declaration order -- second, then first");

1== drop runs at the closing brace ==
new first
new second
both alive
drop second
drop first
dropped in REVERSE declaration order -- second, then first
2 println!("\n== a guard: the resource is the value's lifetime ==");
let mut lock = Lock::new("proc table");
println!("held before = {}", lock.is_held());
{
let guard = lock.acquire();
println!("held inside = {}", guard.is_held());
println!(" ... critical section ...");
// Nothing else can reach `lock` here: the guard holds the only &mut.
// println!("{}", lock.is_held()); // error[E0502]
}
println!("held after = {} <- nobody called release()", lock.is_held());

2== a guard: the resource is the value's lifetime ==
held before = false
acquire proc table
held inside = true
... critical section ...
release proc table
held after = false <- nobody called release()
3 println!("\n== the borrow order inside `acquire` is the whole trick ==");
println!(" read/write through the &mut FIRST,");
println!(" THEN move it into the struct -- after that the guard owns it");
println!("do it the other way round and you get E0499 or E0502");

3== the borrow order inside `acquire` is the whole trick ==
read/write through the &mut FIRST,
THEN move it into the struct -- after that the guard owns it
do it the other way round and you get E0499 or E0502
4 println!("\n== release early, by consuming the guard ==");
{
let guard = lock.acquire();
println!("held inside = {}", guard.is_held());
guard.release(); // takes `self` by value: the guard dies here
println!("held after release() = {} <- and the borrow ended too",
lock.is_held());
}
println!("`pub fn release(self) {{}}` -- an empty body that frees something.");
println!("std's `drop` is exactly that: pub fn drop<T>(_x: T) {{ }}");

4== release early, by consuming the guard ==
acquire proc table
held inside = true
release proc table
held after release() = false <- and the borrow ended too
`pub fn release(self) {}` -- an empty body that frees something.
std's `drop` is exactly that: pub fn drop<T>(_x: T) { }
5 println!("\n== it runs on the paths you forgot ==");
println!("early_return(true):");
early_return(true);
println!("early_return(false):");
early_return(false);
println!("one release site in the source, every path covered");

5== it runs on the paths you forgot ==
early_return(true):
new scoped
bailing out early
drop scoped
early_return(false):
new scoped
ran to the end
drop scoped
one release site in the source, every path covered
6 println!("\n== moving the value moves the duty to release it ==");
let t = Tracked::new("moved");
println!(" handing it to a function that keeps it:");
consume(t);
println!(" ... it was dropped inside `consume`, not here");

6== moving the value moves the duty to release it ==
new moved
handing it to a function that keeps it:
consume got moved
drop moved
... it was dropped inside `consume`, not here
7 println!("\n== Copy XOR Drop, one more time ==");
println!("A guard can never be Copy. A copy would release once per copy,");
println!("which is the double free wearing a hat:");
println!(" error[E0184]: the trait `Copy` cannot be implemented for this");
println!(" type; the type has a destructor");
println!("This is the shape of a spinlock guard -- swap the bool for a");
println!("lock word and Drop for the unlock, and a kernel cannot leave a");
println!("lock held.");
println!("\n**RUN** ../examples/show-errors.sh e0184");
7== Copy XOR Drop, one more time ==
A guard can never be Copy. A copy would release once per copy,
which is the double free wearing a hat:
error[E0184]: the trait `Copy` cannot be implemented for this
type; the type has a destructor
This is the shape of a spinlock guard -- swap the bool for a
lock word and Drop for the unlock, and a kernel cannot leave a
lock held.

**RUN** ../examples/show-errors.sh e0184
}

/// A value that announces its own destruction.
pub struct Tracked {
name: &'static str,
}

impl Tracked {
pub fn new(name: &'static str) -> Tracked {
println!(" new {name}");
Tracked { name }
}
}

impl Drop for Tracked {
fn drop(&mut self) {
println!(" drop {}", self.name);
}
}

/// The resource: a flag that must be put back.
pub struct Lock {
name: &'static str,
held: bool,
}

impl Lock {
pub fn new(name: &'static str) -> Lock {
Lock { name, held: false }
}

pub fn is_held(&self) -> bool {
self.held
}

/// Hand out a guard. The `&mut self` borrow travels into the guard, so
/// while the guard lives this `Lock` cannot be reached any other way.
pub fn acquire(&mut self) -> LockGuard<'_> {
// Set the flag BEFORE the borrow is moved into the struct literal.
self.held = true;
println!(" acquire {}", self.name);
LockGuard { lock: self }
}
}

/// Holding this value IS holding the lock.
pub struct LockGuard<'a> {
lock: &'a mut Lock,
}

impl LockGuard<'_> {
/// Reaching the lock at all goes through the guard while it is alive.
pub fn is_held(&self) -> bool {
self.lock.held
}

/// Release now, rather than at the closing brace.
///
/// `self` is taken by value, so calling this MOVES the guard in and it
/// dies at this closing brace -- running `Drop` and freeing the lock.
pub fn release(self) {}
}

impl Drop for LockGuard<'_> {
/// You never call this. The compiler emits the call wherever the guard's
/// owner stops existing, and there is no path through the program that
/// skips it.
fn drop(&mut self) {
self.lock.held = false;
println!(" release {}", self.lock.name);
}
}

/// Two exits, one release site.
fn early_return(bail: bool) {
let _t = Tracked::new("scoped");
if bail {
println!(" bailing out early");
return;
}
println!(" ran to the end");
}

/// Takes the value by value, so this function is where it dies.
fn consume(t: Tracked) {
println!(" consume got {}", t.name);
}
exit status 0

08 Enums: exactly one of these.

exactly one of these, and nothing else · point at size_of::<Option<&u8>>() == 8

$ cargo run --bin 08_enums
//! 08 — Enums: exactly one of these.▸ 21 more lines
//!
//! A struct is "this AND that" -- a product type. An enum is "this OR that" --
//! a SUM type. Each name is a VARIANT, and the type has exactly that many
//! values. There is no Trap equal to 47.
//!
//! C: #define TIMER 0 an int wearing a costume:
//! int cause = 47; compiles, runs, means nothing
//!
//! Rust: enum Trap { Timer, ... }
//! let c: Trap = 47; error[E0308]
//!
//! A variant may carry data of its own, which is where enums stop being
//! #defines with better manners. In memory that is a DISCRIMINANT (a tag saying
//! which variant) plus room for the largest payload:
//!
//! Trap::PageFault { addr: 0x8000_1000 }
//! +-----+--------------------------+
//! | tag | addr (largest payload) |
//! +-----+--------------------------+
//!
//! Run: cargo run --bin 08_enums

use std::mem::size_of;

fn main() {
1 println!("== unit variants: a closed set of names ==");
let a = Level::Machine;
let b = Level::User;
println!("Level::Machine = {a:?}, Level::User = {b:?}");
println!("a == b -> {}", a == b);
println!("size_of::<Level>() = {} <- 3 variants fit in one byte",
size_of::<Level>());
println!("there is no Level equal to 47; the type has three values");

1== unit variants: a closed set of names ==
Level::Machine = Machine, Level::User = User
a == b -> false
size_of::<Level>() = 1 <- 3 variants fit in one byte
there is no Level equal to 47; the type has three values
2 println!("\n== variants that carry data ==");
// Struct-style fields:
let fault = Trap::PageFault { addr: 0x8000_1000 };
// Tuple-style fields:
let call = Trap::Syscall(63);
// No fields at all:
let tick = Trap::Timer;
println!("{}", show(fault));
println!("{}", show(call));
println!("{}", show(tick));
println!("one value answers two questions at once: what happened, and with what");

2== variants that carry data ==
PageFault { addr: 0x80001000 }
Syscall(63)
Timer
one value answers two questions at once: what happened, and with what
3 println!("\n== how they are built ==");
println!(" Trap::Timer no fields");
println!(" Trap::Syscall(63) tuple fields, built like a call");
println!(" Trap::PageFault {{ addr: 0x8000 }} named fields, built like a struct");

3== how they are built ==
Trap::Timer no fields
Trap::Syscall(63) tuple fields, built like a call
Trap::PageFault { addr: 0x8000 } named fields, built like a struct
4 println!("\n== the layout: a tag plus the biggest payload ==");
println!("size_of::<Trap>() = {}", size_of::<Trap>());
println!("size_of::<usize>() = {} <- the largest payload", size_of::<usize>());
println!("the tag rides along, rounded up for alignment");
println!("size_of::<[Trap; 4]>() = {}", size_of::<[Trap; 4]>());

4== the layout: a tag plus the biggest payload ==
size_of::<Trap>() = 16
size_of::<usize>() = 8 <- the largest payload
the tag rides along, rounded up for alignment
size_of::<[Trap; 4]>() = 64
5 println!("\n== a C API says this with an int and an out-parameter ==");
println!(" int cause;");
println!(" long extra; /* meaningful for SOME causes. which? */");
println!("Here the pairing is enforced: a Timer has no address to reach.");

5== a C API says this with an int and an out-parameter ==
int cause;
long extra; /* meaningful for SOME causes. which? */
Here the pairing is enforced: a Timer has no address to reach.
6 println!("\n== Option<T> is just an enum the library wrote ==");
println!(" enum Option<T> {{ Some(T), None }}");
let found: Option<u32> = Some(7);
let missing: Option<u32> = None;
println!("Some(7) = {found:?}, None = {missing:?}");
println!("size_of::<u32>() = {}", size_of::<u32>());
println!("size_of::<Option<u32>>() = {} <- 4 bytes + a tag, aligned",
size_of::<Option<u32>>());

6== Option<T> is just an enum the library wrote ==
enum Option<T> { Some(T), None }
Some(7) = Some(7), None = None
size_of::<u32>() = 4
size_of::<Option<u32>>() = 8 <- 4 bytes + a tag, aligned
7 println!("\n== niche optimization: sometimes the tag is free ==");
println!("size_of::<&u8>() = {}", size_of::<&u8>());
println!("size_of::<Option<&u8>>() = {} <- SAME SIZE", size_of::<Option<&u8>>());
println!("a reference is never null, so all-zeroes is a bit pattern it can");
println!("never hold -- and that unused pattern becomes `None`");

7== niche optimization: sometimes the tag is free ==
size_of::<&u8>() = 8
size_of::<Option<&u8>>() = 8 <- SAME SIZE
a reference is never null, so all-zeroes is a bit pattern it can
never hold -- and that unused pattern becomes `None`
8 println!("\n== enums are how a kernel writes a closed set ==");
for t in [Trap::Timer, Trap::Syscall(63), Trap::PageFault { addr: 0x8000_1000 },
Trap::Illegal] {
println!(" {:<38} kernel-handled: {}", show(t), t.is_kernel_handled());
}
println!("program 09 is the `match` that reads them");
8== enums are how a kernel writes a closed set ==
Timer kernel-handled: true
Syscall(63) kernel-handled: true
PageFault { addr: 0x80001000 } kernel-handled: true
Illegal kernel-handled: false
program 09 is the `match` that reads them
}

/// Three privilege levels, and nothing else.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Level {
Machine,
Supervisor,
User,
}

/// Why the hardware stopped the program. Some causes carry a detail.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Trap {
/// The timer fired. Nothing else to say.
Timer,
/// A program asked for a service, by number. Tuple-style fields.
Syscall(usize),
/// A memory access failed, at this address. Struct-style fields.
PageFault { addr: usize },
/// The instruction was not one the CPU knows.
Illegal,
}

impl Trap {
/// A taste of program 09: one arm per variant, no catch-all.
pub fn is_kernel_handled(&self) -> bool {
match self {
Trap::Timer => true,
Trap::Syscall(_) => true,
Trap::PageFault { .. } => true,
Trap::Illegal => false,
}
}
}

/// The `Debug` derive prints integers in decimal, and an address is only
/// readable in hex. This is not a `match` lesson -- it just makes the columns
/// below legible.
fn show(t: Trap) -> String {
match t {
Trap::Timer => String::from("Timer"),
Trap::Syscall(num) => format!("Syscall({num})"),
Trap::PageFault { addr } => format!("PageFault {{ addr: {addr:#x} }}"),
Trap::Illegal => String::from("Illegal"),
}
}
exit status 0

09 match: the compiler as code reviewer.

the compiler lists what you forgot · point at the careful column vs. the _ column

$ cargo run --bin 09_match
//! 09 — match: the compiler as code reviewer.▸ 20 more lines
//!
//! Each ARM is a pattern, `=>`, and the value it produces. `match` is an
//! EXPRESSION, which is why it sits on the right of a `let`.
//!
//! let n = match t {
//! Trap::Timer => 0, a variant
//! Trap::Syscall(num) => num, ... binding its field
//! Trap::PageFault { .. } => 1, ... ignoring its fields
//! Trap::Illegal => 2,
//! };
//!
//! EXHAUSTIVENESS is the safety property. Read forwards it is a nuisance; read
//! backwards it is why the type exists. Add a variant next month and the
//! compiler lists every place that now needs a decision -- by file and line,
//! before the kernel boots. Unless you wrote `_`.
//!
//! The rule: `_` for open domains (a number from hardware or a user
//! program), never for closed ones you defined yourself.
//!
//! Run: cargo run --bin 09_match

fn main() {
1 println!("== match is an expression ==");
for t in traps() {
let cost = match t {
Trap::Timer => 1,
Trap::Syscall(num) => num,
Trap::PageFault { .. } => 100,
Trap::Illegal => 0,
};
println!(" {:<38} cost {cost}", show(t));
}

1== match is an expression ==
Timer cost 1
Syscall(63) cost 63
PageFault { addr: 0x80001000 } cost 100
Illegal cost 0
2 println!("\n== the four pattern features you need ==");
for t in traps() {
let label = match t {
// `|` -- one arm, two variants.
Trap::Timer | Trap::Illegal => "no payload to read",
// `{ .. }` -- has fields, do not care.
Trap::PageFault { .. } => "has an address, ignoring it",
// binding -- pull the field out under a name.
Trap::Syscall(_) => "has a number, see below",
};
println!(" {:<38} {label}", show(t));
}

2== the four pattern features you need ==
Timer no payload to read
Syscall(63) has a number, see below
PageFault { addr: 0x80001000 } has an address, ignoring it
Illegal no payload to read
3 println!("\n== binding the payload ==");
for t in traps() {
let detail = match t {
Trap::Syscall(num) => format!("syscall number {num}"),
Trap::PageFault { addr } => format!("faulting address {addr:#x}"),
Trap::Timer => String::from("no detail"),
Trap::Illegal => String::from("no detail"),
};
println!(" {:<38} {detail}", show(t));
}

3== binding the payload ==
Timer no detail
Syscall(63) syscall number 63
PageFault { addr: 0x80001000 } faulting address 0x80001000
Illegal no detail
4 println!("\n== exhaustiveness ==");
println!("Delete one arm and the program does not compile:");
println!(" error[E0004]: non-exhaustive patterns:");
println!(" `Trap::Illegal` not covered");
println!("Its value is not in the match you are writing now, but in the");
println!("twelve you will not remember when you change the enum next month.");

4== exhaustiveness ==
Delete one arm and the program does not compile:
error[E0004]: non-exhaustive patterns:
`Trap::Illegal` not covered
Its value is not in the match you are writing now, but in the
twelve you will not remember when you change the enum next month.
5 println!("\n== the `_` trap ==");
println!("`_` switches the check off forever: a catch-all covers variants");
println!("that do not exist yet, so adding one produces no error and no");
println!("warning. `is_illegal_wrong` below is right today, by luck.");
for t in traps() {
println!(" {:<38} careful {:<5} lazy {}",
show(t), t.is_illegal(), t.is_illegal_wrong());
}
println!("Add a fifth variant and only `is_illegal` breaks the build.");

5== the `_` trap ==
`_` switches the check off forever: a catch-all covers variants
that do not exist yet, so adding one produces no error and no
warning. `is_illegal_wrong` below is right today, by luck.
Timer careful false lazy false
Syscall(63) careful false lazy false
PageFault { addr: 0x80001000 } careful false lazy false
Illegal careful true lazy true
Add a fifth variant and only `is_illegal` breaks the build.
6 println!("\n== ... and when `_` is right ==");
// The number came from a user program: the domain is genuinely open.
for num in [1usize, 63, 64, 9999] {
println!(" syscall {num:<5} -> {}", dispatch(num));
}
println!("`_ => -1` here is correct: any integer may arrive.");

6== ... and when `_` is right ==
syscall 1 -> 0
syscall 63 -> 5
syscall 64 -> 5
syscall 9999 -> -1
`_ => -1` here is correct: any integer may arrive.
7 println!("\n== guards: an `if` on an arm ==");
for t in traps() {
let kind = match t {
Trap::Syscall(num) if num < 64 => "known syscall",
Trap::Syscall(_) => "syscall out of range",
Trap::PageFault { addr } if addr >= KERNBASE => "kernel page fault",
Trap::PageFault { .. } => "user page fault",
Trap::Timer => "timer",
Trap::Illegal => "illegal instruction",
};
println!(" {:<38} {kind}", show(t));
}
println!("A failing guard does NOT leave the match -- matching CONTINUES");
println!("with the next arm. That fall-through is the whole point; testing");
println!("inside the arm body loses it and forces you to restate the");
println!("failure path by hand.");
println!("Guarded arms do not count toward exhaustiveness, so a match");
println!("resting on one still needs a fallback.");

7== guards: an `if` on an arm ==
Timer timer
Syscall(63) known syscall
PageFault { addr: 0x80001000 } kernel page fault
Illegal illegal instruction
A failing guard does NOT leave the match -- matching CONTINUES
with the next arm. That fall-through is the whole point; testing
inside the arm body loses it and forces you to restate the
failure path by hand.
Guarded arms do not count toward exhaustiveness, so a match
resting on one still needs a fallback.
8 println!("\n== matching a pair, to test two values at once ==");
for (level, t) in [(Level::User, Trap::Syscall(63)),
(Level::Supervisor, Trap::Syscall(63)),
(Level::User, Trap::Illegal)] {
let action = match (level, t) {
(Level::User, Trap::Syscall(_)) => "service it",
(Level::Supervisor, Trap::Syscall(_)) => "the kernel called itself?!",
(Level::User, _) => "kill the process",
(_, _) => "panic",
};
println!(" {:<12} {:<26} -> {action}",
format!("{level:?}"), show(t));
}
println!("that is how a transition table becomes one readable block");

8== matching a pair, to test two values at once ==
User Syscall(63) -> service it
Supervisor Syscall(63) -> the kernel called itself?!
User Illegal -> kill the process
that is how a transition table becomes one readable block
9 println!("\n== if let: a one-arm match ==");
let t = Trap::PageFault { addr: 0x8000_1000 };
if let Trap::PageFault { addr } = t {
println!(" if let -> faulted at {addr:#x}");
}

9== if let: a one-arm match ==
if let -> faulted at 0x80001000
10 println!("\n== let ... else: bind, or bail out ==");
println!(" describe_syscall(Trap::Syscall(63)) = {:?}",
describe_syscall(Trap::Syscall(63)));
println!(" describe_syscall(Trap::Timer) = {:?}",
describe_syscall(Trap::Timer));

10== let ... else: bind, or bail out ==
describe_syscall(Trap::Syscall(63)) = Some("syscall 63")
describe_syscall(Trap::Timer) = None
11 println!("\n== matches!: a match that answers yes or no ==");
for t in traps() {
println!(" {:<38} is a fault: {}",
show(t), matches!(t, Trap::PageFault { .. }));
}
println!("\n**RUN** ../examples/show-errors.sh e0004");
11== matches!: a match that answers yes or no ==
Timer is a fault: false
Syscall(63) is a fault: false
PageFault { addr: 0x80001000 } is a fault: true
Illegal is a fault: false

**RUN** ../examples/show-errors.sh e0004
}

/// Where the kernel's own memory starts.
pub const KERNBASE: usize = 0x8000_0000;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Level {
Supervisor,
User,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Trap {
Timer,
Syscall(usize),
PageFault { addr: usize },
Illegal,
}

impl Trap {
/// One arm per variant, no catch-all. Adding a variant breaks this, which
/// is the point.
pub fn is_illegal(&self) -> bool {
match self {
Trap::Illegal => true,
Trap::Timer => false,
Trap::Syscall(_) => false,
Trap::PageFault { .. } => false,
}
}

/// Same answer today. Adding a variant silently classifies it as `false`,
/// and nothing tells you.
pub fn is_illegal_wrong(&self) -> bool {
match self {
Trap::Illegal => true,
_ => false,
}
}
}

fn traps() -> [Trap; 4] {
[Trap::Timer, Trap::Syscall(63), Trap::PageFault { addr: 0x8000_1000 }, Trap::Illegal]
}

/// `_` is right here: the number arrives from a user program, so the domain
/// is open and "anything else" is genuinely the rule.
fn dispatch(num: usize) -> i64 {
match num {
1 => 0,
63 => 5,
64 => 5,
_ => -1,
}
}

/// `let ... else` handles the "bind, or leave" shape kernel code is full of.
fn describe_syscall(t: Trap) -> Option<String> {
let Trap::Syscall(num) = t else {
return None;
};
Some(format!("syscall {num}"))
}

/// The `Debug` derive prints integers in decimal, and an address is only
/// readable in hex. This is not a `match` lesson -- it just makes the columns
/// below legible.
fn show(t: Trap) -> String {
match t {
Trap::Timer => String::from("Timer"),
Trap::Syscall(num) => format!("Syscall({num})"),
Trap::PageFault { addr } => format!("PageFault {{ addr: {addr:#x} }}"),
Trap::Illegal => String::from("Illegal"),
}
}
exit status 0

10 Option<T>: absence with a type of its own.

absence with a type of its own · point at Ok(1) and Err(-1) side by side

$ cargo run --bin 10_option
//! 10 — Option<T>: absence with a type of its own.▸ 15 more lines
//!
//! Rust has no null. A possibly-absent value has a different TYPE, defined in
//! the library rather than the language:
//!
//! enum Option<T> { Some(T), None }
//!
//! Tony Hoare, who put null references into ALGOL W in 1965, called it his
//! "billion-dollar mistake" in 2009. The problem is not that absence exists --
//! it is that in C and Java an absent value has the SAME TYPE as a present one,
//! so the compiler cannot say which dereferences need checking.
//!
//! File* may be NULL. Nothing says so. Nothing checks.
//! Option<File> a different type from File. The compiler makes you look.
//!
//! Run: cargo run --bin 10_option

use std::mem::size_of;

fn main() {
let table = device_table();

1 println!("== a function that might have no answer says so in its type ==");
println!("find_device(&table, 1) = {:?}", find_device(&table, 1));
println!("find_device(&table, 9) = {:?}", find_device(&table, 9));
println!("the second one is not an error and not a crash -- it is `None`");

1== a function that might have no answer says so in its type ==
find_device(&table, 1) = Some(Device { major: 1, name: "console", driver: Some("uart") })
find_device(&table, 9) = None
the second one is not an error and not a crash -- it is `None`
2 println!("\n== opening one up: match ==");
for major in [1u32, 9] {
let msg = match find_device(&table, major) {
Some(d) => format!("major {} is {}", d.major, d.name),
None => String::from("no such device"),
};
println!(" {msg}");
}

2== opening one up: match ==
major 1 is console
no such device
3 println!("\n== ... or if let, when only one case matters ==");
if let Some(d) = find_device(&table, 2) {
println!(" found {} at major {}", d.name, d.major);
}

3== ... or if let, when only one case matters ==
found disk at major 2
4 println!("\n== ... or let ... else, to bind or bail ==");
println!(" device_name(&table, 1) = {:?}", device_name(&table, 1));
println!(" device_name(&table, 9) = {:?}", device_name(&table, 9));

4== ... or let ... else, to bind or bail ==
device_name(&table, 1) = Some("console")
device_name(&table, 9) = None
5 println!("\n== the combinators worth knowing ==");
let found = find_device(&table, 1);
let missing = find_device(&table, 9);
println!(" {:<18} {}", "major 1 (present)", "major 9 (absent)");
println!(" .is_some() {:<18} {}",
found.is_some(), missing.is_some());
println!(" .map(|d| d.name) {:<18} {}",
fmt(found.map(|d| d.name)), fmt(missing.map(|d| d.name)));
println!(" .and_then(|d| d.drv) {:<18} {}",
fmt(found.and_then(|d| d.driver)), fmt(missing.and_then(|d| d.driver)));
println!(" ... .unwrap_or(\"-\") {:<18} {}",
found.map(|d| d.name).unwrap_or("-"),
missing.map(|d| d.name).unwrap_or("-"));
println!(" ... .ok_or(-1) {:<18} {}",
format!("{:?}", found.map(|d| d.major).ok_or(-1)),
format!("{:?}", missing.map(|d| d.major).ok_or(-1)));
println!("`map` transforms what is there and leaves `None` alone;");
println!("`and_then` is for when the step itself may also come up empty;");
println!("`unwrap_or` supplies a fallback; `ok_or` turns absence into an error.");

5== the combinators worth knowing ==
major 1 (present) major 9 (absent)
.is_some() true false
.map(|d| d.name) Some("console") None
.and_then(|d| d.drv) Some("uart") None
... .unwrap_or("-") console -
... .ok_or(-1) Ok(1) Err(-1)
`map` transforms what is there and leaves `None` alone;
`and_then` is for when the step itself may also come up empty;
`unwrap_or` supplies a fallback; `ok_or` turns absence into an error.
6 println!("\n== `?` on an Option: return None early ==");
println!(" driver_of(&table, 1) = {:?}", driver_of(&table, 1));
println!(" driver_of(&table, 3) = {:?} <- device exists, no driver",
driver_of(&table, 3));
println!(" driver_of(&table, 9) = {:?} <- no device at all",
driver_of(&table, 9));

6== `?` on an Option: return None early ==
driver_of(&table, 1) = Some("uart")
driver_of(&table, 3) = None <- device exists, no driver
driver_of(&table, 9) = None <- no device at all
7 println!("\n== Option is how you say `nothing is available` honestly ==");
let mut pool = vec![10u32, 11, 12];
for _ in 0..4 {
match take_one(&mut pool) {
Some(n) => println!(" took {n}, {} left", pool.len()),
None => println!(" nothing left -- and the caller cannot ignore that"),
}
}
println!("compare: C returns 0 or -1 or NULL and hopes you check");

7== Option is how you say `nothing is available` honestly ==
took 12, 2 left
took 11, 1 left
took 10, 0 left
nothing left -- and the caller cannot ignore that
compare: C returns 0 or -1 or NULL and hopes you check
8 println!("\n== the cost ==");
println!("size_of::<u32>() = {}", size_of::<u32>());
println!("size_of::<Option<u32>>() = {} <- 4 bytes plus a tag, aligned",
size_of::<Option<u32>>());
println!("size_of::<&u32>() = {}", size_of::<&u32>());
println!("size_of::<Option<&u32>>() = {} <- FREE: null is the niche",
size_of::<Option<&u32>>());
println!("a reference can never be all-zeroes, so that pattern IS `None`");

8== the cost ==
size_of::<u32>() = 4
size_of::<Option<u32>>() = 8 <- 4 bytes plus a tag, aligned
size_of::<&u32>() = 8
size_of::<Option<&u32>>() = 8 <- FREE: null is the niche
a reference can never be all-zeroes, so that pattern IS `None`
9 println!("\n== what you should almost never write ==");
println!(" .unwrap() panics on None -- in a kernel that halts the machine");
println!(" .expect(\"\") the same, with a message");
println!("Use them in tests and in code where None is genuinely impossible.");
println!("Everywhere else, `match`, `if let`, `unwrap_or`, or `?`.");
9== what you should almost never write ==
.unwrap() panics on None -- in a kernel that halts the machine
.expect("") the same, with a message
Use them in tests and in code where None is genuinely impossible.
Everywhere else, `match`, `if let`, `unwrap_or`, or `?`.
}

/// One row of a device table.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Device {
pub major: u32,
pub name: &'static str,
/// Not every device has a driver bound to it yet.
pub driver: Option<&'static str>,
}

fn device_table() -> [Device; 3] {
[
Device { major: 1, name: "console", driver: Some("uart") },
Device { major: 2, name: "disk", driver: Some("virtio") },
Device { major: 3, name: "null", driver: None },
]
}

/// The answer may not exist, and the return type says so.
pub fn find_device(table: &[Device], major: u32) -> Option<Device> {
for d in table {
if d.major == major {
return Some(*d);
}
}
None
}

/// `let ... else`: bind the value, or leave.
pub fn device_name(table: &[Device], major: u32) -> Option<&'static str> {
let Some(d) = find_device(table, major) else {
return None;
};
Some(d.name)
}

/// `?` on an `Option` returns `None` from the whole function on the first miss.
pub fn driver_of(table: &[Device], major: u32) -> Option<&'static str> {
let d = find_device(table, major)?;
let name = d.driver?;
Some(name)
}

/// "Nothing is available" is a value, not a sentinel and not a panic.
pub fn take_one(pool: &mut Vec<u32>) -> Option<u32> {
if pool.is_empty() {
return None;
}
let last = pool.len() - 1;
Some(pool.remove(last))
}

/// Debug-format an Option in a fixed width, so the two columns line up.
fn fmt<T: std::fmt::Debug>(v: Option<T>) -> String {
format!("{v:?}")
}
exit status 0

11 Arrays, slices, and Vec: three ways to hold N things.

pointer + length, bounds-checked · point at &[u8] is 16 bytes, &[u8; 8] is 8

$ cargo run --bin 11_slices
//! 11 — Arrays, slices, and Vec: three ways to hold N things.▸ 20 more lines
//!
//! [T; N] the values ARE the variable. N is in the type; never grows.
//! Vec<T> OWNS a heap buffer. ptr + len + cap, 24 bytes.
//! &[T] POINTS AT data you do not own. ptr + len, 16 bytes.
//!
//! [u8; 4]
//! +----+----+----+----+
//! | 10 | 20 | 30 | 40 | stack or .bss, size fixed at compile time
//! +----+----+----+----+
//! ^
//! | &arr[1..3]
//! | +-------+-------+
//! +--| ptr | len=2 | a slice: 16 bytes, borrowing the middle two
//! +-------+-------+
//!
//! `[T]` on its own is legal but UNSIZED -- you can never hold one, because
//! the compiler cannot say how much stack to reserve. It is usable only behind
//! a pointer, and that pointer carries the missing length: a FAT POINTER.
//!
//! Run: cargo run --bin 11_slices

use std::mem::size_of;

fn main() {
1 println!("== the three types ==");
let arr: [u8; 8] = [b'k', b'e', b'r', b'n', b'e', b'l', 0, 0];
let owned: Vec<u8> = vec![b'k', b'e', b'r', b'n'];
let view: &[u8] = &arr[..6];
println!("array {:?} at {:p}", &arr[..], arr.as_ptr());
println!("vec {:?} at {:p} <- heap", owned, owned.as_ptr());
println!("slice {:?} at {:p} <- points into the array",
view, view.as_ptr());

1== the three types ==
array [107, 101, 114, 110, 101, 108, 0, 0] at 0x16ce89b28
vec [107, 101, 114, 110] at 0x103779d80 <- heap
slice [107, 101, 114, 110, 101, 108] at 0x16ce89b28 <- points into the array
2 println!("\n== a slice is a fat pointer ==");
println!("size_of::<&u8>() = {:>2} thin", size_of::<&u8>());
println!("size_of::<&[u8; 8]>() = {:>2} thin: the 8 is in the type",
size_of::<&[u8; 8]>());
println!("size_of::<&[u8]>() = {:>2} FAT: address + length",
size_of::<&[u8]>());
println!("size_of::<[u8; 8]>() = {:>2} the data itself", size_of::<[u8; 8]>());
println!("size_of::<Vec<u8>>() = {:>2} ptr + len + cap", size_of::<Vec<u8>>());
println!("&[u8] could point at three elements or three million");

2== a slice is a fat pointer ==
size_of::<&u8>() = 8 thin
size_of::<&[u8; 8]>() = 8 thin: the 8 is in the type
size_of::<&[u8]>() = 16 FAT: address + length
size_of::<[u8; 8]>() = 8 the data itself
size_of::<Vec<u8>>() = 24 ptr + len + cap
&[u8] could point at three elements or three million
3 println!("\n== making slices from ranges ==");
println!("&arr[..] {:?}", &arr[..]);
println!("&arr[..3] {:?}", &arr[..3]);
println!("&arr[3..] {:?}", &arr[3..]);
println!("&arr[2..5] {:?}", &arr[2..5]);

3== making slices from ranges ==
&arr[..] [107, 101, 114, 110, 101, 108, 0, 0]
&arr[..3] [107, 101, 114]
&arr[3..] [110, 101, 108, 0, 0]
&arr[2..5] [114, 110, 101]
4 println!("\n== the prefix that holds real data ==");
let mut buf = [0u8; 16];
let n = read_into(&mut buf);
println!("read_into filled {n} of {} bytes", buf.len());
println!("&buf[..] -> {} bytes, most of them stale", buf.len());
println!("&buf[..n] -> {} bytes, all of them real", buf[..n].len());
println!("text = {:?}", core::str::from_utf8(&buf[..n]).unwrap());
println!("the slice says `this many of those` without a second variable");
println!("that can drift out of sync");

4== the prefix that holds real data ==
read_into filled 6 of 16 bytes
&buf[..] -> 16 bytes, most of them stale
&buf[..n] -> 6 bytes, all of them real
text = "kernel"
the slice says `this many of those` without a second variable
that can drift out of sync
5 println!("\n== one function, every container ==");
println!("checksum(&arr) = {}", checksum(&arr));
println!("checksum(&owned) = {}", checksum(&owned));
println!("checksum(&arr[2..5]) = {}", checksum(&arr[2..5]));
println!("arrays and Vecs convert to slices automatically");
println!("write `&[T]` to read and `&mut [T]` to write, and one signature");
println!("serves a table of any size");

5== one function, every container ==
checksum(&arr) = 641
checksum(&owned) = 432
checksum(&arr[2..5]) = 325
arrays and Vecs convert to slices automatically
write `&[T]` to read and `&mut [T]` to write, and one signature
serves a table of any size
6 println!("\n== &mut [T] is an EXCLUSIVE borrow of the whole run ==");
let mut page = [0u8; 8];
fill(&mut page, 0xff);
println!("after fill: {page:?}");
// let a = &mut page[0];
// let b = &mut page[1]; // error[E0499]: the compiler cannot prove 0 != 1
let (left, right) = page.split_at_mut(4);
left[0] = 1;
right[0] = 2;
println!("split_at_mut: two writers, provably disjoint -> {page:?}");

6== &mut [T] is an EXCLUSIVE borrow of the whole run ==
after fill: [255, 255, 255, 255, 255, 255, 255, 255]
split_at_mut: two writers, provably disjoint -> [1, 255, 255, 255, 2, 255, 255, 255]
7 println!("\n== indexing is checked ==");
println!("table[i] panics on a bad index -- i is yours, provably in range");
println!("table.get(i) returns None -- out of range is normal");
println!("arr.get(3) = {:?}", arr.get(3));
println!("arr.get(99) = {:?}", arr.get(99));
println!("in RISC-V terms `table[i]` is three extra instructions:");
println!(" bgeu a1, t0, .Lpanic unsigned, so it catches both ends");
println!(" slli t1, a1, 3");
println!(" add t1, a0, t1");
println!("a branch that is never taken, and so predicted perfectly");

7== indexing is checked ==
table[i] panics on a bad index -- i is yours, provably in range
table.get(i) returns None -- out of range is normal
arr.get(3) = Some(110)
arr.get(99) = None
in RISC-V terms `table[i]` is three extra instructions:
bgeu a1, t0, .Lpanic unsigned, so it catches both ends
slli t1, a1, 3
add t1, a0, t1
a branch that is never taken, and so predicted perfectly
8 println!("\n== validate untrusted indices at the boundary ==");
for fd in [1usize, 99] {
println!(" get_file({fd}) = {:?}", get_file(fd));
}
println!("A panic in a kernel is not a failed test: the handler prints and");
println!("halts the machine. A user program that can panic the kernel with");
println!("one bad system-call argument owns a denial of service. Check");
println!("where the number ENTERS, once, and index freely after that.");
println!("In C the missing check reads into a neighboring process.");
println!("\n**RUN** ../examples/show-errors.sh e0080");
8== validate untrusted indices at the boundary ==
get_file(1) = Some("stdout")
get_file(99) = None
A panic in a kernel is not a failed test: the handler prints and
halts the machine. A user program that can panic the kernel with
one bad system-call argument owns a denial of service. Check
where the number ENTERS, once, and index freely after that.
In C the missing check reads into a neighboring process.

**RUN** ../examples/show-errors.sh e0080
}

pub const NOFILE: usize = 4;

/// Reads to a slice and reports how many bytes it actually delivered.
fn read_into(buf: &mut [u8]) -> usize {
let msg = b"kernel";
buf[..msg.len()].copy_from_slice(msg);
msg.len()
}

/// One signature, every container: `&[T]` to read.
fn checksum(bytes: &[u8]) -> u32 {
let mut sum = 0u32;
for &b in bytes {
sum = sum.wrapping_add(b as u32);
}
sum
}

/// `&mut [T]` to write.
fn fill(bytes: &mut [u8], value: u8) {
for b in bytes.iter_mut() {
*b = value;
}
}

/// The kernel shape: refuse the index before it ever reaches the array.
fn get_file(fd: usize) -> Option<&'static str> {
if fd >= NOFILE {
return None;
}
let ofile = ["stdin", "stdout", "stderr", "disk"];
Some(ofile[fd])
}
exit status 0

12 Fixed tables, and the iterators that walk them.

a fixed table, searched lazily · point at the closure running twice, not eight times

$ cargo run --bin 12_tables_and_iterators
//! 12 — Fixed tables, and the iterators that walk them.▸ 21 more lines
//!
//! Every operating system keeps lists of things: processes, open files, free
//! pages, buffered keystrokes. Where those lists live, and whether they may
//! grow, is one of the first real design decisions in a kernel.
//!
//! static mut PROCS: [Proc; NPROC] a plain array of 64 slots
//!
//! Three reasons it is an array and not a Vec, and none of them is taste:
//!
//! 1. THERE IS NO ALLOCATOR YET. The table is used on the fourth line of
//! boot. The allocator is itself a data structure and cannot allocate.
//! 2. THE FAULT PATH MUST NOT ALLOCATE. Allocation can fail, can take
//! unbounded time, and takes locks -- none of which a trap handler
//! with interrupts off can survive.
//! 3. A HARD LIMIT FAILS HONESTLY. The 65th fork returns -1, at the call
//! that asked for too much. A Vec fails later, somewhere else.
//!
//! Rust's iterators are LAZY and allocate nothing, which is why this style is
//! legal in a kernel with no heap at all. `collect` is the exception.
//!
//! Run: cargo run --bin 12_tables_and_iterators

use std::mem::size_of;

/// The compile-time bound. Raising it is a code change with a known cost.
pub const NDEV: usize = 8;

fn main() {
let table = device_table();

1 println!("== a fixed table costs what it costs, always ==");
println!("size_of::<DevEntry>() = {}", size_of::<DevEntry>());
println!("size_of::<[DevEntry; {NDEV}]>() = {} = {NDEV} x {}",
size_of::<[DevEntry; NDEV]>(), size_of::<DevEntry>());
println!("reserved at link time, whether one device is live or {NDEV}");
println!("you always pay for the worst case -- and in exchange the worst");
println!("case can never surprise you");

1== a fixed table costs what it costs, always ==
size_of::<DevEntry>() = 32
size_of::<[DevEntry; 8]>() = 256 = 8 x 32
reserved at link time, whether one device is live or 8
you always pay for the worst case -- and in exchange the worst
case can never surprise you
2 println!("\n== iter(): each item is &T, the table is untouched ==");
for e in table.iter() {
println!(" major {:<3} {:<10} reads {}", e.major, e.name, e.reads);
}

2== iter(): each item is &T, the table is untouched ==
major 1 console reads 120
major 2 disk reads 4096
major 3 null reads 0
major 0 - reads 0
major 0 - reads 0
major 0 - reads 0
major 0 - reads 0
major 0 - reads 0
3 println!("\n== enumerate(): kernels care WHERE a thing is ==");
for (index, e) in table.iter().enumerate() {
if e.major != 0 {
println!(" slot {index} holds major {}", e.major);
}
}
println!("enumerate counts from 0 regardless of what it counts:");
println!("on `table[3..].iter().enumerate()` the numbers are NOT the");
println!("table's indices -- a classic off-by-three");

3== enumerate(): kernels care WHERE a thing is ==
slot 0 holds major 1
slot 1 holds major 2
slot 2 holds major 3
enumerate counts from 0 regardless of what it counts:
on `table[3..].iter().enumerate()` the numbers are NOT the
table's indices -- a classic off-by-three
4 println!("\n== position(): the index of the first match ==");
println!("position(major == 2) = {:?}",
table.iter().position(|e| e.major == 2));
println!("position(major == 9) = {:?}",
table.iter().position(|e| e.major == 9));

4== position(): the index of the first match ==
position(major == 2) = Some(1)
position(major == 9) = None
5 println!("\n== find(): the first matching item ==");
match table.iter().find(|e| e.name == "disk") {
Some(e) => println!("found disk at major {}", e.major),
None => println!("no disk"),
}

5== find(): the first matching item ==
found disk at major 2
6 println!("\n== any() / all() / count() / filter() / map() ==");
println!("any(reads > 100) = {}", table.iter().any(|e| e.reads > 100));
println!("all(reads < 1000) = {}", table.iter().all(|e| e.reads < 1000));
println!("count(live) = {}", table.iter().filter(|e| e.major != 0).count());
println!("total reads = {}", table.iter().map(|e| e.reads).sum::<usize>());
println!("busiest = {:?}",
table.iter().filter(|e| e.major != 0).max_by_key(|e| e.reads).map(|e| e.name));

6== any() / all() / count() / filter() / map() ==
any(reads > 100) = true
all(reads < 1000) = false
count(live) = 3
total reads = 4216
busiest = Some("disk")
7 println!("\n== iter_mut(): each item is &mut T, modified in place ==");
let mut counters = table;
for e in counters.iter_mut() {
if e.major != 0 {
e.reads += 1;
}
}
println!("after one pass: {:?}",
counters.iter().filter(|e| e.major != 0).map(|e| e.reads).collect::<Vec<_>>());
println!("which one you may use is decided by your SIGNATURE, not by taste:");
println!("a function that took &[T] borrowed for reading, so iter_mut");
println!("will not compile there");

7== iter_mut(): each item is &mut T, modified in place ==
after one pass: [121, 4097, 1]
which one you may use is decided by your SIGNATURE, not by taste:
a function that took &[T] borrowed for reading, so iter_mut
will not compile there
8 println!("\n== lazy: nothing is built, and it stops early ==");
let mut examined = 0;
let hit = table
.iter()
.map(|e| {
examined += 1;
e
})
.find(|e| e.major == 2);
println!("find(major == 2) -> {:?}", hit.map(|e| e.name));
println!("the closure ran {examined} times, not {NDEV}");
println!("`map` does not produce a list -- it computes one item each time");
println!("`find` asks, and `find` stops asking the moment it succeeds");
println!("the compiled code is the loop you would have written by hand");

8== lazy: nothing is built, and it stops early ==
find(major == 2) -> Some("disk")
the closure ran 2 times, not 8
`map` does not produce a list -- it computes one item each time
`find` asks, and `find` stops asking the moment it succeeds
the compiled code is the loop you would have written by hand
9 println!("\n== collect() is the one adapter that allocates ==");
let names: Vec<&str> = table.iter().filter(|e| e.major != 0).map(|e| e.name).collect();
println!("collect() -> {names:?}");
println!("it has to put the results SOMEWHERE. Host code and tests only,");
println!("until the kernel has a heap.");

9== collect() is the one adapter that allocates ==
collect() -> ["console", "disk", "null"]
it has to put the results SOMEWHERE. Host code and tests only,
until the kernel has a heap.
10 println!("\n== a chain that reads as English ==");
for limit in [100, 1000, 10_000] {
println!(" first live device with more than {limit:>5} reads -> {:?}",
first_busy(&table, limit));
}
println!("skip the empty slots, pair each device with its read count,");
println!("stop at the first busy one, and hand back its name");

10== a chain that reads as English ==
first live device with more than 100 reads -> Some("console")
first live device with more than 1000 reads -> Some("disk")
first live device with more than 10000 reads -> None
skip the empty slots, pair each device with its read count,
stop at the first busy one, and hand back its name
11 println!("\n== the hard limit, and what happens at it ==");
let mut live = device_table();
println!("{NDEV} slots, 3 already taken, so 5 installs should fit:");
for major in 4u32..=11 {
match install(&mut live, major) {
Some(slot) => println!(" major {major:<2} -> slot {slot}"),
None => println!(" major {major:<2} -> None: table full. This is `open` returning -1."),
}
}
println!("The failure lands at the exact call that asked for too much, and");
println!("it is testable: fill the table and check that the next one fails.");
println!("With a Vec the limit is `until memory runs out`, which sounds");
println!("more generous and is much worse: the failure arrives late, at an");
println!("unrelated allocation elsewhere, and you cannot test it.");

11== the hard limit, and what happens at it ==
8 slots, 3 already taken, so 5 installs should fit:
major 4 -> slot 3
major 5 -> slot 4
major 6 -> slot 5
major 7 -> slot 6
major 8 -> slot 7
major 9 -> None: table full. This is `open` returning -1.
major 10 -> None: table full. This is `open` returning -1.
major 11 -> None: table full. This is `open` returning -1.
The failure lands at the exact call that asked for too much, and
it is testable: fill the table and check that the next one fails.
With a Vec the limit is `until memory runs out`, which sounds
more generous and is much worse: the failure arrives late, at an
unrelated allocation elsewhere, and you cannot test it.
12 println!("\n== where Vec DOES belong ==");
println!("Host commands, tests, and the kernel after it has a heap --");
println!("allocated at initialization, never on the trap path.");
println!("`Vec` is not bad. It is a tool with a prerequisite.");
12== where Vec DOES belong ==
Host commands, tests, and the kernel after it has a heap --
allocated at initialization, never on the trap path.
`Vec` is not bad. It is a tool with a prerequisite.
}

/// One row of a fixed device table.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DevEntry {
pub major: u32,
pub name: &'static str,
pub reads: usize,
}

impl DevEntry {
/// `const fn`, so the table below needs no start-up loop.
pub const fn empty() -> DevEntry {
DevEntry { major: 0, name: "-", reads: 0 }
}
}

fn device_table() -> [DevEntry; NDEV] {
let mut t = [DevEntry::empty(); NDEV];
t[0] = DevEntry { major: 1, name: "console", reads: 120 };
t[1] = DevEntry { major: 2, name: "disk", reads: 4096 };
t[2] = DevEntry { major: 3, name: "null", reads: 0 };
t
}

/// Never learns how big the table is: the iterator stops at `table.len()`.
fn first_busy(table: &[DevEntry], limit: usize) -> Option<&'static str> {
table
.iter()
.filter(|e| e.major != 0)
.map(|e| (e.name, e.reads))
.find(|&(_, reads)| reads > limit)
.map(|(name, _)| name)
}

/// Claim a slot, or report honestly that there is none.
fn install(table: &mut [DevEntry], major: u32) -> Option<usize> {
for i in 0..table.len() {
if table[i].major == 0 {
table[i] = DevEntry { major, name: "new", reads: 0 };
return Some(i);
}
}
None
}
exit status 0

E0004 non-exhaustive patterns

a variant was added; a match was not · fix: add the arm, or _ and lose the check

$ ./show-errors.sh e0004
// error[E0004]: non-exhaustive patterns
//
// 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.
//
// FIX 1: add the missing arm and decide what Breakpoint means here.
// FIX 2: `_ => 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.
enum Trap {
Timer,
Syscall(usize),
PageFault { addr: usize },
Illegal,
Breakpoint,
}

fn is_fatal(t: Trap) -> bool {
match t {
Trap::Timer => false,
Trap::Syscall(_) => false,
Trap::PageFault { .. } => false,
Trap::Illegal => true,
}
}

fn main() {
println!("{}", is_fatal(Trap::Breakpoint));
}
Why, and 2 fixes e

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.

  1. add the missing arm and decide what Breakpoint means here.
  2. _ => 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.
$ rustc --edition 2021 --emit=metadata -o /dev/null e0004_non_exhaustive.rs
error[E0004]: non-exhaustive patterns: `Trap::Breakpoint` not covered
|
21 | match t {
| ^ pattern `Trap::Breakpoint` not covered
|
note: `Trap` defined here
|
12 | enum Trap {
| ^^^^
...
17 | Breakpoint,
| ---------- not covered
= note: the matched value is of type `Trap`
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
|
25 ~ Trap::Illegal => true,
26 ~ Trap::Breakpoint => todo!(),
|
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0004`.
exit status 1

E0015 cannot call non-const function in constants

a non-const fn in a const context · fix: make the callee const fn

$ ./show-errors.sh e0015
// error[E0015]: cannot call non-const function in constants
//
// 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.
//
// FIX 1: mark `blocks_for` as `const fn`. Its body is already legal in one.
// FIX 2: if it truly cannot be const, compute the value once at start-up.
fn blocks_for(bytes: u32) -> u32 {
(bytes + 511) / 512
}

const ROOT_BLOCKS: u32 = blocks_for(5000);

fn main() {
println!("{ROOT_BLOCKS}");
}
Why, and 2 fixes e

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.

  1. mark blocks_for as const fn. Its body is already legal in one.
  2. if it truly cannot be const, compute the value once at start-up.
$ rustc --edition 2021 --emit=metadata -o /dev/null e0015_non_const_in_const.rs
error[E0015]: cannot call non-const function `blocks_for` in constants
|
17 | const ROOT_BLOCKS: u32 = blocks_for(5000);
| ^^^^^^^^^^^^^^^^
|
note: function `blocks_for` is not const
|
13 | fn blocks_for(bytes: u32) -> u32 {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: calls in constants are limited to constant functions, tuple structs and tuple variants
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0015`.
exit status 1

E0080 evaluation of constant value failed

a constant index past the end · fix: fix the index, or use get()

$ ./show-errors.sh e0080
// error[E0080]: evaluation of constant value failed
//
// 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.
//
// FIX 1: use an index that is in range.
// FIX 2: `TABLE.get(8)` returns `Option`, and `None` is a normal outcome.
static TABLE: [u32; 4] = [10, 20, 30, 40];

const LAST: u32 = TABLE[8];

fn main() {
println!("{LAST}");
}
Why, and 2 fixes e

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.

  1. use an index that is in range.
  2. TABLE.get(8) returns Option, and None is a normal outcome.
$ rustc --edition 2021 --emit=metadata -o /dev/null e0080_const_index.rs
error[E0080]: index out of bounds: the length is 4 but the index is 8
|
15 | const LAST: u32 = TABLE[8];
| ^^^^^^^^ evaluation of `LAST` failed here
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0080`.
exit status 1

E0184 the trait Copy cannot be implemented for this type; the type has a destructor

Copy on a type with a destructor · fix: drop one of the two

$ ./show-errors.sh e0184
// error[E0184]: the trait `Copy` cannot be implemented for this type; the type
// has a destructor
//
// 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.
//
// FIX 1: drop the `Copy` derive. A guard is meant to have exactly one owner.
// FIX 2: drop the `Drop` impl, if the type really owns nothing.
#[derive(Clone, Copy)]
struct Guard {
id: usize,
}

impl Drop for Guard {
fn drop(&mut self) {
println!("release {}", self.id);
}
}

fn main() {
let a = Guard { id: 1 };
let b = a;
println!("{} {}", a.id, b.id);
}
Why, and 2 fixes e

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.

  1. drop the Copy derive. A guard is meant to have exactly one owner.
  2. drop the Drop impl, if the type really owns nothing.
$ rustc --edition 2021 --emit=metadata -o /dev/null e0184_copy_with_drop.rs
error[E0184]: the trait `Copy` cannot be implemented for this type; the type has a destructor
|
13 | #[derive(Clone, Copy)]
| ---- in this derive macro expansion
14 | struct Guard {
| ^^^^^ `Copy` not allowed on types with destructors
|
note: destructor declared here
|
19 | fn drop(&mut self) {
| ^^^^^^^^^^^^^^^^^^
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0184`.
exit status 1

E0308 mismatched types

a raw integer where a newtype belongs · fix: wrap it, or unwrap it visibly

$ ./show-errors.sh e0308
// error[E0308]: mismatched types
//
// 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.
//
// FIX 1: pass a BlockNo: `read_block(BlockNo(7))`.
// FIX 2: if the conversion is genuinely meant, spell it out -- `BlockNo(n.0)`
// -- so it is one greppable line rather than an invisible accident.
#[repr(transparent)]
#[derive(Clone, Copy)]
struct BlockNo(u32);

#[repr(transparent)]
#[derive(Clone, Copy)]
struct ByteCount(u32);

fn read_block(b: BlockNo) -> u32 {
b.0
}

fn main() {
let n = ByteCount(512);
read_block(n);
read_block(7);
}
Why, and 2 fixes e

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.

  1. pass a BlockNo: read_block(BlockNo(7)).
  2. if the conversion is genuinely meant, spell it out -- BlockNo(n.0) -- so it is one greppable line rather than an invisible accident.
$ rustc --edition 2021 --emit=metadata -o /dev/null e0308_newtype_mismatch.rs
error[E0308]: mismatched types
|
27 | read_block(n);
| ---------- ^ expected `BlockNo`, found `ByteCount`
| |
| arguments to this function are incorrect
|
note: function defined here
|
21 | fn read_block(b: BlockNo) -> u32 {
| ^^^^^^^^^^ ----------
error[E0308]: mismatched types
|
28 | read_block(7);
| ---------- ^ expected `BlockNo`, found integer
| |
| arguments to this function are incorrect
|
note: function defined here
|
21 | fn read_block(b: BlockNo) -> u32 {
| ^^^^^^^^^^ ----------
help: try wrapping the expression in `BlockNo`
|
28 | read_block(BlockNo(7));
| ++++++++ +
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0308`.
exit status 1

E0382 use of moved value

a by-value self consumed the value · fix: derive Copy, or take &self

$ ./show-errors.sh e0382
// error[E0382]: use of moved value
//
// `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.
//
// FIX 1: `#[derive(Clone, Copy)]` -- the call then copies four bytes.
// FIX 2: take `&self` in both methods -- borrowing never moves. That is the
// answer for anything too big or too resource-owning to copy.
#[derive(Clone)]
struct Entry(u32);

impl Entry {
fn flags(self) -> u32 {
self.0 & 0x3f
}

fn is_valid(self) -> bool {
self.flags() & 1 != 0
}
}

fn main() {
let e = Entry(0x2000_0007);
println!("{} {}", e.flags(), e.is_valid());
}
Why, and 2 fixes e

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.

  1. #[derive(Clone, Copy)] -- the call then copies four bytes.
  2. take &self in both methods -- borrowing never moves. That is the answer for anything too big or too resource-owning to copy.
$ rustc --edition 2021 --emit=metadata -o /dev/null e0382_method_moved_it.rs
error[E0382]: use of moved value: `e`
|
27 | let e = Entry(0x2000_0007);
| - move occurs because `e` has type `Entry`, which does not implement the `Copy` trait
28 | println!("{} {}", e.flags(), e.is_valid());
| ------- ^ value used here after move
| |
| `e` moved due to this method call
|
note: `Entry::flags` takes ownership of the receiver `self`, which moves `e`
|
17 | fn flags(self) -> u32 {
| ^^^^
help: you can `clone` the value and consume it, but this might not be your desired behavior
|
28 | println!("{} {}", e.clone().flags(), e.is_valid());
| ++++++++
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0382`.
exit status 1

E0507 cannot move out of self.name which is behind a shared reference

moving a field out of &self · fix: return a borrow, or clone()

$ ./show-errors.sh e0507
// error[E0507]: cannot move out of `self.name` which is behind a shared
// reference
//
// `&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.
//
// FIX 1: return a borrow: `fn name(&self) -> &str { &self.name }`.
// FIX 2: `self.name.clone()` -- a second buffer, allocated visibly.
// FIX 3: take `self` by value if the Device really is finished with.
struct Device {
name: String,
major: u32,
}

impl Device {
fn name(&self) -> String {
self.name
}
}

fn main() {
let d = Device { name: String::from("uart0"), major: 1 };
println!("{} {}", d.name(), d.major);
}
Why, and 3 fixes e

&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.

  1. return a borrow: fn name(&self) -> &str { &self.name }.
  2. self.name.clone() -- a second buffer, allocated visibly.
  3. take self by value if the Device really is finished with.
$ rustc --edition 2021 --emit=metadata -o /dev/null e0507_move_out_of_self.rs
error[E0507]: cannot move out of `self.name` which is behind a shared reference
|
20 | self.name
| ^^^^^^^^^ move occurs because `self.name` has type `String`, which does not implement the `Copy` trait
|
help: consider cloning the value if the performance cost is acceptable
|
20 | self.name.clone()
| ++++++++
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0507`.
exit status 1