Feature Integrations

Ergon ships optional integrations behind Cargo feature flags — add exactly what your hot path needs, keep compilation lean otherwise.

Quick reference

FeatureBest forCost
compact_strTickers, symbols, venue codes (≤24 B)0 alloc, 46–56% faster than String at ≤24 B; converges at 32 B+
smol_strLong-lived DTOs, shared/cached objectsO(1) clone; from_utf8 cost grows with size (5–25 ns)
bytesRelay/forwarding, zero-copy pipelinesCompetitive at all sizes, best at 256 B+
chronoTyped timestamps on encode/decode+2–6 ns vs raw i64 (4–8× slower but negligible vs I/O)

See Measured performance below for the full benchmark table.

Add features in your Cargo.toml:

[dependencies]
ergo-sbe = { version = "0.1", features = ["compact_str", "chrono"] }

CompactString — inline symbols (DTOs)

Primarily for domain DTOs. The codec-level accessor into_<field>_as_compact_str() exists but is secondary — the main win is replacing String with CompactString in generated *Domain structs.

compact_str::CompactString stores up to 24 bytes on the stack. Perfect for tickers ("AAPL"), currency pairs ("EUR/USD"), venue codes ("XNYS") — every string that fits in a CPU register-sized inline buffer skips the allocator entirely.

Domain DTOs

use ergo_sbe::{DomainVarData, GenerationConfig};

let config = GenerationConfig::new("msgs")
    .with_domain_objects(DomainVarData::CompactStrings);

Generated DTO:

// Type paths use ergo_sbe re-exports — no need to add compact_str directly.
pub struct QuoteDomain {
    pub symbol: ergo_sbe::compact_str::CompactString,  // was String
    pub venue: ergo_sbe::compact_str::CompactString,
    pub price: Decimal,
    // …
}

Codec accessors

When compact_str is enabled, every text var-data consuming stage gains an into_<field>_as_compact_str() method:

let stage = dec.fuel_figures()?;
let (symbol, next_stage) = stage.into_symbol_as_compact_str()?;
// symbol: CompactString — no heap allocation for ≤24B symbols

Measured performance

aarch64-apple-darwin, rustc 1.95.0 — nanoseconds per from_utf8 + conversion

PayloadStringCompactStringSmolStrBytesVec<u8>
3 B (ticker)5.9 ns3.2 ns (−46%)5.4 ns8.6 ns10.1 ns
8 B (venue)5.8 ns3.1 ns (−47%)5.4 ns8.7 ns10.1 ns
24 B (inline limit)8.2 ns3.6 ns (−56%)5.1 ns9.7 ns10.5 ns
32 B (over inline)10.2 ns14.2 ns10.0 ns10.7 ns10.9 ns
128 B11.1 ns13.9 ns14.3 ns17.2 ns11.1 ns
256 B19.1 ns19.2 ns25.3 ns13.6 ns14.1 ns

Takeaway: CompactString is 46–56% faster for symbols ≤24 bytes (no heap). At larger sizes it converges with String. Bytes wins at 256 B+. SmolStr has O(1) clone regardless of length; from_utf8 cost grows with payload size.

Run: cargo bench -p ergo-sbe-benchmarks --bench var_data_types_bench --all-features

SmolStr — cheap clones (DTOs)

Primarily for domain DTOs. Like CompactString, the main use is DomainVarData::SmolStrings in generated *Domain structs.

smol_str::SmolStr clones in O(1) regardless of length. Best when DTOs are long-lived and shared across threads or cached.

let config = GenerationConfig::new("msgs")
    .with_domain_objects(DomainVarData::SmolStrings);

Codec: into_<field>_as_smol_str() — returns SmolStr.

Bytes — zero-copy relay

bytes::Bytes is a reference-counted byte buffer. Clone it without copying; slice it without allocating. Ideal for relay/forwarding pipelines where the same frame is dispatched to multiple consumers.

Domain DTOs

let config = GenerationConfig::new("relay")
    .with_domain_objects(DomainVarData::BytesCrate);

Generated DTO fields are bytes::Bytes:

let original = bytes::Bytes::copy_from_slice(b"payload");
let clone = original.clone();   // increments refcount, no copy
let sub = original.slice(4..);  // view into the same buffer

Codec accessors

into_<field>_as_bytes() returns bytes::Bytes:

let (payload, next) = stage.into_payload_as_bytes()?;
// payload: bytes::Bytes — share it, slice it, send it across threads

Chrono — typed timestamps

SBE timestamp fields are i64 on the wire (nanoseconds or microseconds since the Unix epoch). The chrono feature adds converter functions and enables with_domain_type for timestamp semantic types.

Build-time config

use ergo_sbe::{GenerationConfig, ConversionSelector};

let config = GenerationConfig::new("msgs")
    .with_domain_type(
        ConversionSelector::semantic_type("UTCTimestamp"),
        "chrono::DateTime<chrono::Utc>",
    )
    .with_domain_type(
        ConversionSelector::semantic_type("UTCTimestampMicros"),
        "chrono::NaiveDateTime",
    );

Generated API

// Decode
let created: chrono::DateTime<chrono::Utc> = dec.try_created_at()?;
let updated: chrono::NaiveDateTime = dec.try_updated_at()?;

// Encode
enc.try_created_at(chrono::Utc::now())?;
enc.try_updated_at(chrono::DateTime::from_timestamp(1_720_000_000, 0).unwrap().naive_utc())?;

Registering semantic_type("UTCTimestamp") once covers every field in the schema carrying that semanticType, not just created_at — see One selector, many fields for a worked multi-field example.

Direct converters

use ergo_sbe::chrono_converters;

// Wire nanos → DateTime
let dt = i64_nanos_to_datetime(1_720_000_000_000_000_000);

// DateTime → wire nanos
let ns = datetime_to_i64_nanos(dt);

// Wire micros → NaiveDateTime
let naive = i64_micros_to_naive(1_720_000_000_000_000);

// Roundtrip is exact
assert_eq!(naive_to_i64_micros(naive), 1_720_000_000_000_000);

Measured conversion cost

aarch64-apple-darwin, rustc 1.95.0 — ns per operation

OperationTimevs raw i64Notes
i64_nanos_to_datetime2.8 ns4.1×Wire → DateTime<Utc> (decode)
datetime_to_i64_nanos4.8 ns7.0×DateTime<Utc> → wire (encode)
i64_micros_to_naive5.5 ns8.1×Wire → NaiveDateTime (decode)
naive_to_i64_micros5.6 ns8.2×NaiveDateTime → wire (encode)
raw i64 no-op0.68 nsbaselineIdentity pass-through

Takeaway: Conversions add 2–6 ns — negligible next to the I/O cost of a network frame (~500 ns for 10 GbE) or the allocator cost of a var-data field (3–19 ns). The type safety is worth it.

Run: cargo bench -p ergo-sbe-benchmarks --bench chrono_converter_bench --all-features

Combining features

All four features are independent — enable any subset:

[dependencies]
ergo-sbe = { version = "0.1", features = ["compact_str", "bytes", "chrono"] }
let config = GenerationConfig::new("msgs")
    .with_domain_objects(DomainVarData::CompactStrings)
    .with_domain_type(
        ConversionSelector::semantic_type("UTCTimestamp"),
        "chrono::DateTime<chrono::Utc>",
    );