- Integration test: smoke test against in-process EchoServer (hello handshake round-trip). Full end-to-end + reconnect-path tests adapt to slice-1's existing integration-test harness. - examples/echo_brain/: Python reference brain (~80 lines, websockets lib). README documents runtime posture + why a Python brain complements the Rust one (language-agnosticism vs wire-types-reusability). - LEARNING.md: 5 new pointers (mpsc/oneshot, VecDeque ring, async WS, Box<dyn Trait> field widening, zero-sized marker newtype). Spec ref: 2026-06-28-slice-2-agent-tap-design.md §8.4, §8.5 #7.
4.8 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. -
mpsc+oneshotfor cold-path task supervision →crates/rutster/src/tap_engine.rs— how a spawned tokio task is supervised + cancelled viaoneshot::Receiver. -
VecDequeas a bounded playout ring with drop-oldest policy →crates/rutster-tap/src/tap_audio_pipe.rs— why a manual ring (notmpsc) when the overflow policy is drop-oldest, not drop-newest. -
Async WS connect +
Sink/Streamtraits →crates/rutster-tap/src/tap_client.rs—tokio_tungstenite::connect_async,WebSocketStream, theSinkExt/StreamExtextension traits,tokio::select!over inbound + outbound + close. -
Box<dyn Trait + Send>field widening (the seam test) →crates/rutster-media/src/rtc_session.rs— why thepipefield type changed fromEchoAudioPipetoBox<dyn AudioSource + AudioSink + Send>soloop_driver's call sites are byte-identical (slice-2 §8.5 #6). -
Zero-sized marker newtype for state flags →
crates/rutster-call-model/src/lib.rs—TapHandle(())compilesOption<TapHandle>to a singlebool; no runtime cost for the type-system marker.
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.