Skip to content

Prep: Errors, and Your First Command — 08r · 10c

Session: Fri Sep 18, 1h30 · Exercises: 08r_errors 10c_echo · Prep time: ~40 min · Lecture: Traits, Generics, and the ulib Façade · Buffers, Bytes, and Line-Oriented I/O

What you will build

First, a chain of fallible steps over a toy directory: a scan that reports absence with Option, a call that turns absence and an over-long name into failure with your own error enum, steps glued together with ?, and a boundary where the Result collapses into one Unix-style integer — a byte count, or minus an errno number. Second, echo, your first command, against the ulib façade: arguments in as byte slices, bytes out through write_all on fd 1, exit status 0. The tests check that each bad name yields the right error variant (never a panic), and that echo emits byte-exact output for zero, one, several, and empty arguments.

Concepts you need

Read before class

What Time
L06 §6 Errors as Values (§6.1–6.3, §6.5) 15 min
L06 §7.3 Testing through the seam 3 min
L07 §1, §3, §5 (last two paragraphs), §7 (echo paragraph) 12 min
ulib guide: The complete API surface, Portability rules 10 min

Mental model

Waking a process by pid — not a filesystem, same three layers:

enum ProcError { NoSuchPid, NotSleeping }

fn slot_of(table: &[Proc], pid: u32) -> Option<usize> { /* scan */ }

fn wakeup(table: &mut [Proc], pid: u32) -> Result<(), ProcError> {
    let i = slot_of(table, pid).ok_or(ProcError::NoSuchPid)?;   // absence becomes failure HERE
    if table[i].state != State::Sleeping { return Err(ProcError::NotSleeping); }
    table[i].state = State::Runnable;
    Ok(())
}

fn sys_wakeup(table: &mut [Proc], pid: u32) -> i64 {           // the boundary: one integer
    match wakeup(table, pid) { Ok(()) => 0, Err(ProcError::NoSuchPid) => -3, Err(ProcError::NotSleeping) => -22 }
}

The scan states a fact; the middle layer decides, and may use ? only because it returns Result; the edge collapses everything into the one register a user program can receive. A kernel has no exception to throw and nothing to unwind into, so every system call you write from 50k on has this shape.

Check yourself

  1. A directory scan finds no entry for a name. Option or Result, and where is "missing means the call failed" decided?
    AnswerThe scan returns Option — absence is not an error there. The caller that promised to open something converts with .ok_or(SomeVariant); that one line is where the policy lives.
  2. Printing items x, ` (empty),ywith a space **separator**: how many spaces, and what would "a space after every item" give instead? <details><summary>Answer</summary>Two, givingx y— the empty item still counts. "After every item" is a terminator and leaves a trailing space.nitems needn − 1` separators; the newline goes once, after the loop.
  3. Why write_all and never write, when the tests pass either way?
    Answerwrite may accept fewer bytes than offered and only says so in its count. The host harness accepts everything, so bare write is green on your laptop and truncates on rv6 in December. write_all loops until every byte is out.

What "done" looks like

oslings run is green, then oslings submit before you leave. Not green? Submit anyway (substantial credit), then finish by Monday 11:59 pm and submit again.

If you finish early

Rustlings error_handling and options groups: https://github.com/rust-lang/rustlings. 100 Exercises To Learn Rust, chapter 5 (enums, fallibility, Result, ?): https://rust-exercises.com/100-exercises/. Then start Thursday's prep page on cat.