Traits as Extension Points
A trait in a skeleton is a hole that someone else will fill: the agent now, or another team later. It needs the same care as the types around it.
/// A sink for decoded signal values. Implementations must be cheap to
/// call. Batching and I/O belong behind the trait, not in front of it.
pub trait SignalSink {
/// Called once per decoded frame, in arrival order.
fn accept(&mut self, signals: &DecodedSignals) -> Result<(), SinkError>;
}
Properties of a well-designed extension point:
- It has as few methods as possible. Each method is a burden on every implementor, and an agent will happily generate many methods if the trait invites it.
- Its doc comments state the contract: ordering, threading, and performance expectations. Implementors, including agents, rely on them.
- Itβs sealed unless external implementations are intended. The sealed trait pattern lets outside code name the trait but not implement it, which keeps your options open.
A useful review question for any trait: does it need to exist? A trait with one implementation and no test double can often be replaced by the concrete type, or by a function.