LEARNING.md indexes ten concept-to-file pointers (the spec floor was five) — the newtype pattern, exhaustive enum match, sans-IO, trait extension seams, thiserror + hot-path match-and-continue, Arc<Mutex> vs Arc<RwLock>, DashMap, str0m's single-mutation invariant, graceful shutdown, include_str!. fuzz/README.md pre-paves the layout (no hostile-bytes surface in slice 1; harnesses land at step 5 per the out-of-scope table). README's new dev-loop section documents the libopus FFI prerequisite and the manual e2e steps.
3.7 KiB
LEARNING.md — to learn concept X, read file Y
This index maps a Rust concept you might be learning to the file where
slice 1 makes the concept concrete. Each entry is a worked example you
can read in cargo doc --open plus the source file itself.
Concepts + pointers
-
Newtype pattern (type-safety via single-field wrappers) →
crates/rutster-call-model/src/lib.rs—ChannelId(Uuid). The newtype stops us from mixing up aChannelIdwith some futureSessionIdat the type-system level. Compile-enforced where a comment could only ask. -
enumfor closed state sets + exhaustivematch→crates/rutster-call-model/src/lib.rs—ChannelState(New → Connecting → Connected → Closing → Closed). Exhaustiveness checking forces everymatchto consider each state; adding a state later surfaces every site that needs handling. -
Sans-IO pattern (no I/O inside the library; input via method calls, output via return values) →
crates/rutster-media/src/loop_driver.rs— the str0m poll loop.Rtc::handle_inputtakes a network packet as a struct argument, not from a socket the library owns;poll_outputreturnsTransmitpackets the caller sends. Fully testable without a network — str0m integration tests use this property to drive faster than realtime. -
Trait design for extension points (a futures-compatible seam) →
crates/rutster-media/src/pcm.rs— theAudioSource/AudioSinktraits. Slice 1 wires anEchoAudioPipebetween them; step 2 swaps that for a real WSS tap client without touchingRtcSession. The traits describe what the splice point does, not how it's filled. -
Error enums with
thiserror+ hot-path match-and-continue →crates/rutster-media/src/lib.rs(MediaError) andcrates/rutster-media/src/opus_codec.rs(OpusDecoder::decodereturnsOption<PcmFrame>). Cold path:thiserror-derived enum +?. Hot path: match-and-continue, never?, never panic — "drop + observe, don't crash" (spec §3.8). -
Arc<Mutex<T>>vsArc<RwLock<T>>— when each is right →crates/rutster/src/session_map.rs. TheRtcSessionlives behindArc<Mutex<...>>because every access mutates it (str0m's&mut selfcontract) —RwLock's read-mode would be useless. Comment on the struct explains the trade-off. -
DashMapfor sharded concurrent maps →crates/rutster/src/session_map.rs.DashMapshards its inner map so two handlers operating on differentChannelIds don't contend;HashMapwrapped in a singleMutexwould serialize every access. -
str0m 0.21's single-mutation invariant →
crates/rutster-media/src/loop_driver.rs. Mutate (handle_input / Writer::write) → drainpoll_outputtoOutput::Timeout→ next mutate. Violating this leaves str0m in an inconsistent state. -
tokio graceful shutdown via signal handlers →
crates/rutster/src/main.rs(shutdown_signal). Ctrl-C / SIGTERM drops the AppState; the AppState drops the DashMap; the DashMap drops every RtcSession. No in-flight call preservation in slice 1. -
include_str!for embedding static assets →crates/rutster/src/routes.rs(include_str!("../static/index.html")). The HTML test client is compiled into the binary at build time — no separate file to ship, no disk IO to serve it.
How to read
cargo doc --open— every module has a//!doc comment; the doc tree is the high-level map.- Pick a concept above; open the named file. The first occurrence of
each non-obvious pattern has a
//comment explaining why. - Cross-ref back to the spec sections cited inline (
spec §3.8,ADR-0002, etc.) for the architecture-level rationale.