Introduction

ergo-sbe generates zero-allocation Rust codecs from Simple Binary Encoding (SBE) schemas with official-SBE wire fidelity inside the published profile (compatibility). Wire-order safety is enforced at compile time — calling methods out of schema order is a type error. Every maintained ergon/sbe-tool comparison is gated at a literal 1.00 ceiling under both LTO profiles; quote a result by run id, not by a number copied into this page. See Benchmarks.

Quick start

# Cargo.toml
[build-dependencies]
ergo-sbe = "0.1"
#![allow(unused)]
fn main() {
let mut buf = [0u8; HeartbeatEncoder::compute_length_with_header()];
// Buffer is exact size from const compute_length_with_header — no bounds check needed.
let len = HeartbeatEncoder::try_wrap_and_apply_header(&mut buf, 0)?
    .fixed(&HeartbeatFixedFields { sequence: 7, timestamp: 0 })
    .encoded_length_with_header();
let dec = HeartbeatDecoder::try_from(&buf[..len])?;
assert_eq!(dec.sequence(), 7);
}

Set up → a 3-minute path from zero to working codec. All API surfaces are covered in the Feature Tour → with compilable examples.

Feature overview

ergo-sbe
Wire-order safetyCompile-time type-state stages — calling asks before bids is a type error, not a runtime bug
Exact buffer sizingcompute_length_with_header(…) gives the exact byte count before you encode — no oversize scratch buffers, works directly with Aeron try_claim
Closure-based groupsbids(n, |g| g.add(|e| { … })) — nests like the schema, no .parent() hopscotch
Trust boundarydecode / try_from / wrap return Result and validate extents; public unsafe *_unchecked only after an external extent proof
Composite wire images#[repr(transparent)] Engine([u8; N]) — the value IS the on-wire bytes, zero-copy with portable LE/BE accessors
Domain typesMap wire Decimal to rust_decimal::Decimal at the codec boundary — one line of config, no hand-rolled converters
Bulk group opsbulk_add(&[Entry]) / bulk_decode() — measured about 22-23% lower encode latency than add() for 1,000-entry flat groups on the audited Apple M4 profiles
Zero dependencies at runtimeGenerated codecs embed their own sbe_rt — no ergo-sbe on your critical path

ergon is an experimental Rust workspace. ergo-sbe generates the codecs; ergo-aeron-cluster is a client-only Aeron Cluster experiment built on rusteron. Neither crate is production-ready today. APIs may change. Verify wire compatibility, failure handling, and performance for your own schemas. Exit criteria: Road to 1.0.

ergo-sbe parses Simple Binary Encoding (SBE) schemas and generates Rust codecs that match the official SBE wire layout for the features listed in the compatibility profile (header, field layout, groups, var-data, byte order — see docs/SBE_COMPATIBILITY.md).

It is not a line-for-line port of the java/rust sbe-tool stubs. The goals for the generated API are:

  1. Easier to use — especially nested groups and var-data under Rust’s borrow checker
  2. Safer — wire order and trust boundaries enforced by types / Result
  3. Easier to read — nested structure looks like the schema, not a pile of temporary handles

Still built for low-latency official-SBE wire work. The style uses named stage structs (not Encoder<State> generics), closures + method chaining for groups, checked entry points, version-aware accessors, and optional domain/conversion helpers.

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:

  // Nested shape mirrors the schema — no .parent() hopscotch.
  enc.fixed(&fields)
      .bids(n, |bids| {
          bids.add(|level| {
              level.price(p).size(s);
              level.orders(m, |ords| {
                  ords.add(|o| { o.order_id(id); Ok(()) })?;
                  Ok(())
              })?;
              Ok(())
          })?;
          Ok(())
      })?
      .asks(0, |_| Ok(()))?
      .symbol(b"EURUSD")?;

Wire parity is exercised three ways: official Java .sbe fixtures, live dual-encode suites that require ergo-sbe and sbe-tool Rust bytes to be identical (sbe_tool_wire_parity_test for deep Car matrices; sbe_tool_multi_schema_wire_parity_test across example/unit schemas with checked-in sbe-tool reference crates under sbe/tests/sbe_tool_reference/), and a maintained benchmark gate versus sbe-tool-generated codecs (see BENCHMARKS.md).

Early release (0.x). Experimental APIs may change. Binary wire compatibility is covered by an automated suite (golden bytes, schema edge cases, parity benches). Pin versions and report production use — real-world feedback is how the experimental banner goes away.

Workspace

PathPackageRole
sbe/ergo-sbeSBE schema parser and Rust codec generator
sbe/benchmarks/ergo-sbe-benchmarksUnpublished parity benchmarks
cluster/ergo-aeron-clusterExperimental Aeron Cluster client
samples/seven standalone cratesUnpublished integration playgrounds

The workspace requires Rust 1.88 or newer. The sample crates are intentionally excluded from the Cargo workspace and remain publish = false.

Set up

The repository pins the upstream Aeron and SBE repositories as submodules:

git submodule update --init --recursive

Common local checks:

just policy
just check-products
just test
RUSTDOCFLAGS="-D warnings" cargo doc -p ergo-sbe --all-features --no-deps
RUSTDOCFLAGS="-D warnings" cargo doc -p ergo-aeron-cluster --no-deps

just test is intentionally not a partial/offline green path: it builds and runs the Java Cluster lifecycle/recovery lane and the HA sample. Use just test-all to add Miri and deterministic fuzz replay.

Pull-request CI also enforces the non-decreasing coverage baseline. Scheduled lanes run every fuzz target for ten minutes, Miri fixtures weekly, and critical-path mutation testing weekly. Missing or empty results fail closed.

See the samples README for standalone sample commands and Java harness requirements. Run just --list for the repository's available build, test, interoperability, and benchmark recipes.

Project boundaries

  • Official SBE wire compatibility takes priority over API convenience.
  • Maintained hot paths are compared with the official SBE generator output.
  • Benchmark claims are parity-checked and profile-specific. The corrected suite treats a repeatable sbe-tool win as a blocking benchmark/codegen defect and publishes results with LTO both enabled and disabled.
  • Codec microbenchmarking is notoriously easy to get wrong. Benchmark results are explicitly reviewable evidence, not product claims; surprising ratios should be reported and treated as suspected benchmark defects first.
  • Checked entry points must report malformed input rather than manufacture default, empty, or lossy values.
  • The Cluster crate implements a client, not a consensus module, service container, archive, backup node, or Cluster administration tool.
  • Samples, benchmarks, Java harness code, and upstream reference sources are repository support material, not publication targets.