` may now write `version(pkt)?`
- A short packet comes out as `Err(BootError { stage: 1, cause: HdrError::TooShort })`
- Without the `impl`: E0277, "`?` couldn't convert the error to `BootError`"
---
## The one thing to get right · `08r`
A `?` sits in a function that returns `i64`. What does rustc say?
``error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` …``
`?` is an early `return Err(e)`, and a bare integer has no `Err` side to carry it.
? can only leave through a return type that has an Err.
Notes:
- As taught, one of the four traps the Sep 15 plan never cut. Only the function at the system-call boundary returns a bare number; everything below it returns `Result` and uses `?`.
- ↓ Problem 3 traces a boundary function by hand, with numbers.
- **Q:** "Can `main` use `?`?" Only if `main` returns a `Result`; then an `Err` ends the program with a nonzero status. A system call cannot: the user program must get one integer.
--
## Problem 3: Trace the exits
A boundary function returns `version`'s byte on success, −1 for `TooShort` and −2 for `BadMagic`.
**(a)** What does it return for `b"RV\x07"`, `b"RV\x00"`, `b"RV"`, `b"R"`, `b""`, `b"XY\x07"`, `b"rv\x07"` and `b"XY"`?
**(b)** Someone deletes the `?` at the end of `version`'s first line. Which line does rustc now reject, and why?
**(c)** Why could the boundary function not use `?`?
--
## Problem 3 · solution
| Packet | `version` | Boundary |
|---|---|---|
| `b"RV\x07"` | `Ok(7)` | 7 |
| `b"RV\x00"` | `Ok(0)` | 0 |
| `b"RV"` | `Err(TooShort)`: the magic passes, byte 2 is missing | −1 |
| `b"R"`, `b""` | `Err(TooShort)`: `get(0..2)` is `None` | −1 |
| `b"XY\x07"`, `b"rv\x07"`, `b"XY"` | `Err(BadMagic)`: `b"rv"` is not `b"RV"` either | −2 |
The second row is why failure is negative: 0 is a real version. **(b)** The comparison `magic != b"RV"`, with E0308, "expected `Result<&[u8], HdrError>`, found `&[u8; 2]`". Without `?`, `magic` is still the whole `Result`, not the two bytes inside it; `?` is what takes them out. **(c)** `?` is an early `return Err(e)`, and a function returning `i64` has no `Err` to return: E0277.
---
## Friday · `10c`: words in, bytes out, a number back
```text
$ sort -r names.txt
argv[0] = "sort" argv[1] = "-r" argv[2] = "names.txt" argc = 3
```
- `argv[0]` is `sort` itself; `-r` is `argv[1]`
- A **command** answers with bytes on **standard output** (fd 1) and an **exit status** (0: it worked)
- Arguments are bytes: each is a `&[u8]`
- `b"-r"` is a `&[u8; 2]`, `"-r"` a `&str`: mixing them is E0308
Notes:
- In December `oslings ship` rebuilds your commands to run at your own rv6 shell's prompt. `10c_echo` is the first. This part runs about 17 minutes.
- The shell keeps the exit status in `$?`. `exec` copies raw bytes and nobody checks them, so `Args::get` answers `Option<&[u8]>`: past the end is `None`. The E0308 reads "expected `&[u8]`, found `&str`".
- **Q:** "Why not `&str`? Everything I type is text." A `&str` promises valid UTF-8, and keeping that promise means checking every byte. `exec` copied bytes and promised nothing, so bytes are the honest type.
---
## The one thing to get right · `10c`
A test fails on two strings that look the same. Why?
One has a blank before its newline.
- **Separator**: only between items, like `:` in `PATH`
- **Terminator**: after every item, like `\n` in a text file
- An empty item keeps its place: `/bin::/usr/bin` has three entries
Decide which one you are printing before you write the loop.
Notes:
- As taught, one of the four traps the Sep 15 plan never cut. `assert_eq!` prints both strings in quotes: look for the space before the `\n`.
- The prep's Check yourself question 2 is this slide, with an empty item in the middle.
- **Q:** "Where else does this matter?" Anywhere a list is printed. A text file ends each line with a `\n` terminator, which is why `wc -l` reports one line fewer for a file with no final newline.
---
## `write_all`, and the short write
`write(fd, bytes)` is a request, and its answer is a count that may be smaller than what you offered:
```text
write_all(1, b"hello, os\n") 10 bytes; the device takes at most 4 per call
write -> Ok(4) "hell" 6 left
write -> Ok(4) "o, o" 2 left
write -> Ok(2) "s\n" 0 left: write_all returns Ok(())
```
- `Ok(())` means all of it: there is no partial success
- A `write` that returns 0 would spin that loop forever, so `write_all` makes it an error
Notes:
- Midterm 1 scope: "why `write_all` exists". A nearly full disk, or a signal that arrives mid-write, can take 4 of your 10 bytes and return `Ok(4)`, with no error and no warning. `write_all` asks again, re-pointing its slice past what went out.
- The test harness takes every byte in one call, so a bare `write` passes every test and breaks on the first short write. ↓ Problem 4 works one by the numbers.
- **Q:** "What if a `write` fails halfway?" `write_all` returns that `Err` at once. The bytes already written stay written, and the caller learns the whole message did not go out.
--
## Problem 4: Short writes by the numbers
A device accepts at most 5 bytes per `write`, and a program sends `b"stat: ok\n"`, 9 bytes.
**(a)** With one bare `write`, what does it return, and what arrives?
**(b)** With `write_all`, how many `write` calls happen, and what does each return?
**(c)** The device starts answering `Ok(0)`. What does `write_all` do?
**(d)** Why would the program in (a) pass its tests on your laptop?
--
## Problem 4 · solution
**(a)** `Ok(5)`. `"stat:"` arrives and `" ok\n"` never does. Nothing reports the loss unless the program compares 5 with 9.
**(b)** Two: `Ok(5)` for `"stat:"`, then `Ok(4)` for `" ok\n"`. Then `write_all` returns `Ok(())`.
**(c)** It returns `Err(Error(-1))` at once, instead of asking forever for bytes the device will not take.
**(d)** The harness's `write` takes every byte in one call, so a short write never happens there. A green test proves the logic, not the handling of the count.
---
## One file, two targets: the `ulib` seam
Your command calls **`ulib`**, a **façade**: one small API, two implementations.
- Built for your laptop: each call goes to the host
- Built for rv6: each call is an `ecall`
- The `#[cfg(target_os = "none")]` that picks lives in `ulib`, so your command never chooses
The rv6 terms: no heap, an image of at most 64 KiB, bytes only.
Notes:
- "Built for rv6" means built for `riscv64gc-unknown-none-elf`. The ulib guide's portability table lists the substitutes: fixed arrays for `Vec`, `write_all` and `write_usize` for `write!`. As taught, the in-class plan cut this material first when short.
- The tests drive your command through `ulib`'s host side and capture what it writes, which is why a bare `write` passes them. ↓ below: why `target_os`, and not a Cargo feature.
- **Q:** "With no `main` on rv6, where does the program start?" `ulib::main!` writes the entry point for each target, and the ulib guide's `ulib::main!` section shows both. Your function is the same in both.
--
## Why `target_os`, and not a Cargo feature
`ulib` could pick its backend with `#[cfg(feature = "rv6")]` and a `--features rv6` flag. That fails two ways:
- Forget the flag when building for RISC-V, and the host backend compiles for a machine with no `std`: the errors name `std`, never the flag
- Cargo unifies features across a build, so one crate that enables `rv6` enables it for every crate, host tests included
`target_os` comes from the `--target` flag itself, so the backend cannot disagree with the machine. Features should add abilities, never choose between alternatives.
---
## Summary
- A slice is an address and a length; `&` only reads
- A live iterator is the only way into its table
- Kernel tables are fixed arrays
- A generic body is checked once, against its bounds
- One copy per type list used; `dyn`: one copy, a vtable
- `Option` is absence, `Result` failure; `.ok_or` decides, `?` carries
- Bytes in, `write_all` out, one number back
Notes:
- One sentence per bullet, in the order of the page.
- **Q:** "How should I study this for Midterm 1?" Practice Set 1 Problem 3 on paper, then the page's Problems 1 to 4. The For the exam section covers sentinels and counting copies.
---
## [Before Thursday](../prep/04-cs326-2026-09-17-prep-collections-and-traits/): check yourself
You build a miniature process table, then an output sink and a scheduling policy behind traits, all under `cargo test`. Prep: about 20 minutes.
**1.** A function takes `states: &[ProcState]`. Can its body call `states.iter_mut()`?
**2.** `fn log(s: &mut S, line: &str)` is called with three sink types. How many copies of `log` exist, and what changes with `&mut dyn Sink`?
**3.** A system call hands the kernel an unchecked index. Why is `table[i]` the wrong first move?
Notes:
- Walk the prep page (about 20 minutes of prep). What you build: a miniature of rv6's process table, a fixed array of slots; then an output sink with one required method and a default, and a scheduling policy driven by a loop that never learns which policy it holds.
- No `oslings doctor` this week: all four exercises run under `cargo test`. The three questions are the prep's own Check yourself (06r-iterating, exam-copies, thu-06r); answers are on the prep page.
- **Q:** "Where do I start on Thursday?" Read the tests first. They are the contract, and each test's name is a sentence about what your code must do.
---
## [Before Friday](../prep/04-cs326-2026-09-18-prep-errors-and-echo/): check yourself
You build a chain of fallible steps that ends in one integer, then `echo`, your first command. Prep: about 20 minutes.
**1.** A directory scan finds no entry for a name. `Option` or `Result`, and where is "missing means the call failed" decided?
**2.** Printing items `x`, an empty item, and `y` with a space **separator**: how many spaces, and what would "a space after every item" give instead?
**3.** Why `write_all` and never `write`, when the tests pass either way?
Notes:
- Walk the prep page (about 20 minutes). What you build: `08r`, fallible steps over a toy directory, glued with `?`, ending at a boundary that returns one integer; then `10c`, `echo` against the `ulib` façade, with arguments in as byte slices and bytes out through `write_all` on fd 1.
- The three questions are the prep's own Check yourself (08r-error-enum, 10c-one-thing, 10c-write-all); answers are on the prep page.
- **Q:** "Will my `echo` really run on rv6?" In December: `oslings ship` rebuilds the same file for rv6, and it runs at your own shell's prompt, if it kept the three rv6 terms on 10c-seam.
---
## Next week
[Week 5 · Streams of Bytes: `cat`, `wc`, and `grep`](05-cs326-2026-09-22-cat-wc-and-grep/): read its Essentials before Tuesday.
Notes:
- Week 5 builds three commands on this week's `&[u8]`, `write_all` and `Result`. Its short read is the mirror image of today's short write.
- **Q:** "Is Week 5 on Midterm 1?" Yes: the short-read contract and streaming with fixed buffers and O(1) state are in scope. Midterm 1 is Thursday, October 15.