NotFound
```
The collapse belongs in **one** place: the boundary. Everything behind it speaks `FsError`.
---
## `ulib`: the Design Problem
- September: you write `cat.rs` on a laptop; `cargo test` grades it
- December: you have a RISC-V kernel with files, fds, `exec`, user mode
- Goal: the **same source file** — same bytes, not a port — runs on both
The two environments share nothing:
| | laptop | rv6 |
|---|---|---|
| `read` | libc → macOS/Linux | `ecall`, number in `a7` |
| `std` | yes | no |
| heap | yes | no |
---
## One Seam, Two Backends
flowchart TD
SRC["commands/src/bin/cat.rs\none file, zero cfg attributes"]
SRC --> U["ulib\nread / write / open / close / exit"]
U --> C{"target_os == none ?"}
C -->|no| H["sys/host.rs\nstd::fs, std::io,\nor a capture buffer"]
C -->|yes| R["sys/rv6.rs\necall, a7 = syscall number"]
H --> HOST["cargo test, September"]
R --> K["your rv6 kernel, December"]
---
## The Whole Mechanism
```rust
// ulib/src/sys/mod.rs
#[cfg(target_os = "none")]
mod rv6;
#[cfg(target_os = "none")]
pub(crate) use rv6::*;
#[cfg(not(target_os = "none"))]
mod host;
#[cfg(not(target_os = "none"))]
pub(crate) use host::*;
```
Both backends expose the same five functions, so `lib.rs` above them has **no `#[cfg]` at all**. Your command file has none either — two lines of ceremony (`cat.rs:15`, `cat.rs:19`).
---
## Why Not a Cargo Feature?
- `target_os` is **derived** from `--target`: one source of truth, cannot disagree with the machine you are building for
- **Forget** `--features rv6` on a RISC-V build → a wall of `std`, `panic_handler`, `eh_personality` errors that never say "you forgot a feature"
- **Set it wrongly** on the host → a program that runs `ecall` on macOS, from a build that warned about nothing
- Features are **additive and unified**: one crate enabling `ulib/rv6` enables it everywhere in the graph
Features add capabilities. They do not choose between alternatives.
---
## Testing Through the Seam
```rust
let out = testing::run_with_files(
&["cat", "a.txt"],
&[("a.txt", b"hello\nworld\n")],
super::run,
);
assert_eq!(out.out(), "hello\nworld\n");
```
`testing::run` (`testing.rs:28`) diverts writes into a thread-local capture buffer and calls your `run` directly — no process spawned, no temp files, and **byte-identical source** to what will run on rv6.
---
## Summary
1. **No runtime** — every abstraction is assembled at compile time
2. **Trait** = named contract; required methods plus free default methods
3. **Bound** = what the body may assume and the caller must prove
4. **Monomorphization** = one copy per combination used; calls direct and inlined
5. **`dyn`** = one indirect call, one copy, and the ability to *store* the thing
6. **Failure is a value**: `Option` for absence, `Result` for failure, `?` + `From` to propagate
7. **`ulib`** = one seam, two backends, chosen by `#[cfg(target_os = "none")]`
---
## The Exercises: September 17 and 18
**`07r_traits`** (Thursday, September 17) — build both kernel abstractions: `Out` with two sinks, `Scheduler` with two policies, and one generic function driving every combination.
**`08r_errors`** (Friday, September 18) — a tiny filesystem: `find` (absence), `lookup` (failure), `read`, `read_file` chained with `?`, and the boundary that turns it all into one integer.
```bash
oslings run 07r_traits
oslings watch # re-runs on every save
oslings hint # when stuck
```