() == 4
size_of::<[Pte; 512]>() == 512 * 8 == 4096 one page
```
That is what makes an array of 512 entries a genuine hardware page table rather
than a Rust convenience: no tag, no padding.
The cost is that pte + 1 becomes a type error until you write a
method. Which is the point.
---
## Packing Fields Into One Word
Four bit operations do all the work anywhere in a kernel:
```text
a >> n shift every bit down n places
a << n shift every bit up n places
a | b set every bit set in either — how two fields MERGE
a & mask keep only the bits set in both — how one field is SELECTED
```
A Unix permission word, three 3-bit fields side by side:
```rust
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) & 0o7 }
```
Build with shifts and an OR; read back with a shift and a mask.
---
## `const fn`: Arithmetic the Compiler Does
```rust
pub const fn kib(n: usize) -> usize { n * 1024 }
const KERNEL_STACK: usize = kib(8); // 8192 is baked into the image
let n = kib(pages); // ordinary call, ordinary instructions
```
const only adds an ability. The same function is
still callable at run time, on a value that does not exist until then.
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.
---
## Which Path a Call Takes
flowchart LR
A["kib(8)"] --> B{"in a const context?"}
B -- "const / static / array length" --> C["const-eval inside rustc"]
C --> D["the literal 8192 in the image"]
B -- "no" --> E["code generation"]
E --> F["shifts and adds at run time"]
Inside a `const fn` you may use arithmetic, comparisons, `if`, `match`, `loop`,
and other `const fn`s. You may **not** allocate, call a non-`const` function, or
dereference a raw pointer.
---
## Why a Kernel Needs It
```rust
static SLOTS: [Slot; 8] = [const { Slot::new() }; 8];
```
Eight fully initialized records, in the executable, with **no code running**.
That matters because of *when* the data must be valid: when the kernel's first
Rust function is entered there is no heap, no allocator, and nothing that could
have run an initialization loop.
C static initializers allow constant expressions only — = 0 yes, a
function call no. Hence the pile of xxx_init() routines in every C
kernel whose only job is to fill in what the language could not.
**RUN** `cargo run --bin 05_const_fn` code + output ↗ then `../examples/show-errors.sh e0015` rustc output ↗
---
## `#[repr(C)]`: Layout as a Contract
By default a struct has representation `repr(Rust)`, and the language promises
**nothing**: not field order, not padding, not that two identical declarations
agree.
```rust
struct Header { flag: u8, addr: u64, count: u16 }
```
| | `repr(Rust)` | `#[repr(C)]` |
|---|---|---|
| `size_of` | 16 | 24 |
| `flag` at | offset 10 | offset 0 |
| `addr` at | offset 0 | offset 8 |
| `count` at | offset 8 | offset 16 |
The compiler put the most-aligned field first, and it was entitled to.
---
## You Need It When Something Else Reads the Bytes
```text
Context (#[repr(C)]) swtch (hand-written asm; a0 = old)
offset +--------------------------+
0 | ra return address | <--> sd ra, 0(a0)
8 | sp stack pointer | <--> sd sp, 8(a0)
16 | s0 | <--> sd s0, 16(a0)
+--------------------------+
```
Assembly, hardware, a C library, or a file somebody else wrote.
Delete #[repr(C)] 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 context switch.
**RUN** `cargo run --bin 06_repr_c` code + output ↗
---
## Worse Than Breaking
`Context`'s fourteen fields are all `usize`, so today's compiler has no reason
to reorder them — and the kernel would probably keep running.
The bug is not that it breaks. It is that nothing promises it will not.
It would break on a compiler upgrade, or the day someone changes
ra to a u32.
| Attribute | Guarantee | Used for |
|---|---|---|
| `repr(Rust)` | none; optimize freely | everything internal |
| `#[repr(C)]` | source order, C alignment | `Context`, `Trapframe`, disk images |
| `#[repr(transparent)]` | identical to the one field | `Pte`, every newtype |
| `#[repr(packed)]` | no padding at all | nothing — refs to unaligned fields are UB |
---
# Part III
## `Drop`, and the guard
---
## `free()` Became a Closing Brace
When a value goes out of scope the compiler emits a call to its type's `Drop`.
```rust
impl Drop for LockGuard<'_> {
fn drop(&mut self) {
self.lock.held = false;
}
}
```
You do not write the call. You cannot forget it. 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.
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.
---
## A Guard Is a Value Whose Existence Is the Resource
```rust
let mut lock = Lock::new("proc table");
{
let guard = lock.acquire();
// ... critical section ...
// lock is unreachable here: the guard holds the only &mut
} // <- released, by the compiler
```
Two properties earn this its place in a kernel. It is **deterministic**, so a
drop can carry real work — returning a page, releasing a lock, re-enabling
interrupts. And it runs **wherever the owner ends**.
**RUN** `cargo run --bin 07_drop_guard` code + output ↗
---
## The Order Inside `acquire` Is the Trick
```rust
pub fn acquire(&mut self) -> LockGuard<'_> {
self.held = true; // read and write through the &mut FIRST
LockGuard { lock: self } // ONLY THEN move it into the struct
}
```
After that second line the guard holds the only `&mut`. Reach for the lock
again — or inside the struct literal itself — and you get E0499 or E0502.
This is the mistake to make on screen. It is exactly the trap in
04r's constructor, in different clothes.
---
## Release Travels With Ownership
```rust
pub fn release(self) {}
```
An empty body that frees something. `self` is taken **by value**, so calling
`guard.release()` moves the guard in, and it dies at that closing brace —
running `Drop` and releasing early.
The standard library's drop is this function and nothing more:
pub fn drop<T>(_x: T) { }
Handing a value away hands away the duty to release it.
---
# Part IV
## Enums: exactly one of these
---
## A Sum Type
A struct is "this *and* that". An enum is "this *or* that".
```rust
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Trap { Timer, Syscall(usize), PageFault { addr: usize }, Illegal }
```
Each name is a **variant**, and the type has four values in total — not four
valid values out of four billion.
```c
/* xv6, in C: an int wearing a costume */
p->state = 47; /* compiles. runs. means nothing. */
```
Two holes: nothing rejects `47`, and nothing tells you which of the twelve
`switch` statements needs revisiting when a fifth state is added.
---
## Variants Carry Data
```rust
pub enum Trap {
Timer, // no fields
Syscall(usize), // tuple fields — built like a call
PageFault { addr: usize }, // named fields — built like a struct
Illegal,
}
```
One value answers two questions at once: what happened, and with what.
A C API says this with an int, an out-parameter, and a convention
about which is meaningful when. Here the pairing is enforced — a
Timer has no address to reach.
---
## What It Costs
```text
Trap::PageFault { addr: 0x8000_1000 }
+-----+--------------------------+
| tag | addr (largest payload) | rounded for alignment
+-----+--------------------------+
```
And sometimes nothing at all:
```rust
size_of::<&u8>() // 8
size_of::