Prep: Structs, impl, and const fn — 04r¶
Session: Thu Sep 10, 1h45 · Exercises: 04r_structs_impl · Prep time: ~30 min · Lecture: Week 3 · Structs, impl, Enums, and match
What you will build¶
Three types, under plain cargo test. A half-open region of physical memory that knows its extent, is made from a page count, and can list every whole page inside itself — kinit in miniature, on top of a const fn that rounds an address up to a page boundary the way xv6's PGROUNDUP does. An Sv39 page table entry, a newtype around a 64-bit word whose const fn methods pack a physical page number above ten flag bits and pull both back out. And a PageGuard: one page taken off that free list, exclusively borrowing the list while it is held, and put back by its Drop at the closing brace — 02r's give_back with the remembering removed, and the SpinLockGuard of exercise 37k with the lock swapped for a page. The three chain together. One given test already proves the newtype is eight bytes and a #[repr(C)] struct keeps source order; the rest check that your packing matches the hardware layout, round-trips an address, evaluates at compile time, and that a page comes back exactly once.
Concepts you need¶
- Structs and struct literals — Week 3 · Structs and
impl· Rust for Systems: Structs andimplblocks - Methods vs associated functions:
.vs::— Week 3 · Structs andimpl· Rust for Systems: Structs andimplblocks - The three receivers and
#[derive(Copy)]— Week 3 · The three selves, andderive· Rust for Systems:Copytypes do not move - Newtypes,
#[repr(transparent)], and bit packing — Week 3 · The newtype, and bits in a word · Rust for Systems: The newtype pattern const fnand const contexts — Week 3 · Settled before run time:const fn· Rust for Systems:const fnDrop, RAII, and the guard pattern — Week 3 ·Drop, and the guard, The guard's destination · Rust for Systems: Drop · The guard pattern#[repr(C)]: layout as a contract — Week 3 · Settled before run time:#[repr(C)], Why assembly needs#[repr(C)]· Rust for Systems:#[repr(C)]and why layout matters
Read before class¶
| What | Time |
|---|---|
Week 3 · This week, then Thursday · 04r Structs, methods, and the guard through The newtype, and bits in a word |
8 min |
Week 3 · Settled before run time: const fn and #[repr(C)] and Drop, and the guard |
6 min |
Week 3 · For the exam: Why assembly needs #[repr(C)]. Not needed today; on Midterm 1 |
1 min |
Rust for Systems: Structs, impl, methods, const fn, newtypes, #[repr(C)] |
7 min |
| Rust for Systems: The guard pattern | 2 min |
Mental model¶
A Unix wait status is one 16-bit word: exit code in bits 15..8, signal number in bits 7..0. Give the word a type and the fields methods:
#[repr(transparent)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct WaitStatus(pub u16);
impl WaitStatus {
pub const fn exited(code: u16) -> WaitStatus { WaitStatus(code << 8) } // no self
pub const fn code(self) -> u16 { self.0 >> 8 } // by value
pub const fn signaled(self) -> bool { self.0 & 0xff != 0 }
}
const OK: WaitStatus = WaitStatus::exited(0); // compile time
Pack by shifting each field into its slot and ORing; unpack by shifting back and ANDing with a mask. WaitStatus::exited(3) goes through the type with :: because no value exists yet; st.code() goes through a dot, and st.signaled() after it still compiles because Copy copies two bytes rather than moving st. Being const fn, OK is a literal in the binary. Kernel values that are easy to confuse wear a type this way (a page-table entry, a block of saved registers), so an entry cannot be passed where an address was wanted, and tables of them exist before any code runs.
Check yourself¶
- In
let s = Slot::empty(); s.is_free();, which call is a method and which an associated function?Answer
Slot::empty()is associated: called through the type with::; no value exists yet.s.is_free()is a method: called with a dot on a value, so its first parameter is a form ofself. - A by-value
selfmethod on an eight-byte struct withoutCopyis called twice on one variable. What happens?Answer
The first call moves the value; the second isE0382, use after move. DerivingCopymakes the call copy eight bytes instead:Copymakes assignment stop moving. - What does each need: (a) a static array of 64 process records, correct before the first instruction; (b) a saved-register struct that assembly reads at "base plus 8"?
Answer
(a) Aconst fnto build one record: the linker lays out astatic, and nothing has run to fill it. (b)#[repr(C)], or Rust may reorder fields and offset 8 stops being the stack pointer. It fails silently: the symptom is a garbage jump on a context switch. - A type holds a page taken from a free list and puts it back in
Drop. Why can it never#[derive(Copy)], and what breaks if it could?Answer
error[E0184]: a type with a destructor cannot beCopy. WithCopy, every bitwise copy is a full value of its own; withDrop, every value runs its release when it dies. Together they would run the release once per copy — the page goes back on the free list twice, is handed to two callers, and both write to it. That is the double free from 02r, which is where the rule comes from. A guard that also holds the free list by&mutfails one step earlier, witherror[E0204]: an exclusive borrow cannot be copied either.
What "done" looks like¶
oslings run is green, then oslings submit before you leave. Not green? Submit anyway — the tests that pass earn their share — then finish it at a make-up session (office hours, on the class network) for three quarters, or anywhere else for half, and submit again.
If you finish early¶
- Rustlings (github.com/rust-lang/rustlings): the
structsgroup, thenprimitive_typesfor tuples. - 100 Exercises To Learn Rust (rust-exercises.com/100-exercises): chapter 3, "Ticket v1," then chapter 4, "Traits," for
Copyand derive. - Start reading Friday's prep page, Enums and match.