Wire Order via Named Stages
Why not parent hopping?
sbe-tool's flyweight API uses .parent() to hand ownership back up the tree.
In Rust that pattern hits the borrow checker: move a group encoder in, get
stuck returning the parent, lose the thread of the code.
ergo-sbe leans on scoped closures and chaining so nested schemas stay readable and you rarely pass encoder ownership field-to-field by hand:
#![allow(unused)] fn main() { pub fn encode_sample_car(buf: &mut [u8]) -> Result<usize, sbe_rt::EncodeError> { let mut extras = OptionalExtras::default(); extras.cruise_control(true).sports_pack(true); // Buffer pre-sized from EncodedLength; try_* still validates extent. let len = CarEncoder::try_wrap_and_apply_header(buf, 0) .unwrap() .fixed(&CarFixedFields { serial_number: 1234, model_year: 2013, available: true.into(), code: Model::A, some_numbers: [10, 20, 30, 40], vehicle_code: [b'A', b'B', b'C', b'D', b'E', b'F'], extras, engine: Engine::new( 2000, 4, [b'1', b'2', b'3'], 0i8, false.into(), Booster::new(BoostType::TURBO, 210), ), }) .fuel_figures(2, |g| { g.add(|mut e| { e.speed(30).mpg(35.9); e.usage_description(b"Urban") })?; g.add(|mut e| { e.speed(60).mpg(25.0); e.usage_description(b"Highway") })?; Ok(()) })? .performance_figures(1, |g| { g.add(|mut e| { e.octane_rating(95); e.acceleration(2, |a| { a.add(|x| { x.mph(30).seconds(4.0); Ok(()) })?; a.add(|x| { x.mph(60).seconds(7.5); Ok(()) }) }) })?; Ok(()) })? .manufacturer(b"Honda")? .model(b"Civic VTi")? .activation_code(b"abcdef")? .encoded_length_with_header(); Ok(len) } }
(Real code from the sbe-feature-tour sample — compiles and runs in CI.)
Wire order via named stage structs
SBE is a positional wire format: groups and var-data appear in a fixed schema order with no per-field tags on the wire. That matters a lot in financial markets, where it is common to have two nearly identical repeating groups back-to-back — e.g. bids then asks (same entry layout, different meaning). If you encode or decode them in the wrong order, the bytes still look like a valid message: prices and sizes land in the opposite book side. You only discover the disaster at runtime (wrong trades, inverted books, silent corruption). Compile-time order exists so that mistake becomes a type error while you still have the schema in front of you, not a production incident.
Order is enforced with the same idea as the classic type-state pattern
(Encoder<State> / PhantomData), but not that implementation.
Each wire-order transition returns a named concrete stage struct (e.g.
CarAfterFuelFigures). The H: HeaderState generic on every stage is a
zero-sized orthogonal marker for header-present vs body-only mode — it tracks
header capability, not wire-order progression. Duplicating the stage graph
for HeaderPresent and HeaderAbsent would provide no latency advantage.
All maintained SBE parity comparisons pass at or below the 1.00× ceiling
under both LTO-on and LTO-off profiles. Current results are in
Benchmarks; see the methodology page for reproduction.
Generated code emits separate types for each stage, same fields, different methods:
// Approximate generated shape — not Encoder<AfterBids>:
pub struct BookEncoder<'a> { /* buf, pos, … */ }
pub struct BookAfterBids<'a> { /* same layout */ }
pub struct BookAfterAsks<'a> { /* same layout */ }
// …
impl BookEncoder<'a> {
pub fn bids(self, …) -> Result<BookAfterBids<'a>, …> { … }
// no asks() here — bids first on the wire
}
impl BookAfterBids<'a> {
pub fn asks(self, …) -> Result<BookAfterAsks<'a>, …> { … }
// no bids() here — already done
}
So after fixed fields you may only call the next group/var-data in schema
order. Calling asks before bids is a type error (BookEncoder has no
asks method). Decoders use the same idea: consuming stages
(BookDecoder → BookDecoderAfterBids → …).
Group bodies use |g| { g.add(|e| { … }) } so the outer encoder is not left
half-borrowed while you fill nested levels — the closure ends, then chaining
continues. That is intentional API ergonomics for Rust (avoids .parent()
style ownership hand-offs that fight the borrow checker on deep books).