Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Domain DTOs

Use when you want owned values (Vec groups, owned tails) and simple structs — not the zero-copy hot path. DTOs allocate on decode; the flyweight path does not. A DTO-vs-flyweight latency A/B was not measured.

For re-encode, eligible flat groups are bulk-written directly from &[EntryDomain]: no temporary Vec<Entry> and no encode-time allocation. Eligibility requires fixed-size entries whose domain fields have the same wire representation; nested groups, var-data, optional/versioned fields, configured domain conversions, and bool remapping use the general add path. Integer min/max checks are preserved in both paths. with_all_enums_as_option() / with_null_as_option maps a required enum to Option<Enum> on the DTO: None writes the schema NullVal image (including on a reused buffer), and bulk_add_domain / to_wire_entry use that same mapping.

DTO re-encode of eligible flat groups is a DTO-to-DTO diagnostic (group_encode_bench), not an ergon/sbe-tool ratio. Read a fresh run; do not quote numbers from this page.

#![allow(unused)]
fn main() {
// build.rs — DomainVarData picks the DTO field type for var-data:
// .with_domain_objects(DomainVarData::Strings) // String (invalid UTF-8 → InvalidUtf8 error)
// .with_domain_objects(DomainVarData::Bytes)        // Vec<u8> (byte-exact)
}

Generated shape (illustrative — your names follow your schema):

pub struct QuoteDomain {
    pub seq: u32,
    pub some_numbers: [u32; 4],
    pub vehicle_code: [u8; 6],
    pub qty: u32,
    pub legs: Vec<QuoteLegsEntryDomain>,
    pub note: Vec<u8>,              // Bytes|String per DomainVarData
}
impl QuoteDomain {
    // Named methods, not TryFrom/From: two fallible sources (decoder vs framed
    // slice+offset), and materialisation is never infallible.
    pub fn try_from_decoder(dec: QuoteDecoder<'_>) -> Result<Self, DecodeError>;
    pub fn try_from_slice_with_header(buf: &[u8], offset: usize) -> Result<Self, DecodeError>;
    pub fn encode(&self, buf: &mut [u8]) -> Result<usize, EncodeError>;
    pub fn encoded_length_with_header(&self) -> Result<usize, EncodeError>;
}

Wire → DTO → wire round-trip (the docs fixture uses DomainVarData::Bytes):

#![allow(unused)]
fn main() {
  // Encode a message first (the usual flyweight path)
  let mut buf = [0u8; QuoteEncoder::compute_length_with_header(1, 2)];
  let len = QuoteEncoder::wrap_and_apply_header(&mut buf, 0)
      .fixed(&QuoteFixedFields {
          seq: 1,
          some_numbers: [1, 2, 3, 4],
          vehicle_code: *b"ABCDEF",
          qty: 10,
      })
      .legs(1, |legs| {
          legs.add(|leg| { leg.value(99); Ok(()) })?;
          Ok(())
      })?
      .note_as_str("hi")?
      .encoded_length_with_header();
  // Decode → owned DTO (allocates — not for the hot path)
  let dec = QuoteDecoder::try_from(&buf[..len])?;
  let mut dto = QuoteDomain::try_from_decoder(dec)?;
  assert_eq!(dto.seq, 1);
  assert_eq!(&dto.note, b"hi");
  dto.qty = 500;
  // Re-encode (integer min/max checked; eligible groups use bulk write)
  let n = dto.encode(&mut buf)?;
  assert_eq!(n, len);
}

with_domain_objects(DomainVarData)

SBE <data> is length-prefixed bytes. The enum picks the DTO field type:

CallField typeInvalid UTF-8When to use
.with_domain_objects(DomainVarData::Strings)StringInvalidUtf8 error (strict; 0.1.10)Text schemas when validity is known
.with_domain_objects(DomainVarData::Bytes)Vec<u8>n/a (raw copy)Binary tails or byte-exact re-encode

Strings rejects invalid UTF-8. Materialise returns InvalidUtf8 for bad bytes; there is no silent empty-string fallback. Use Bytes (or stay on flyweights) when you need audit / replay fidelity of non-UTF-8 tails.

Runnable demo (text path): sbe-feature-tour uses DomainVarData::Strings. Flyweight path is unchanged: with schema characterEncoding="UTF-8" you still get into_manufacturer_as_str() without a DTO.

demo_car_domain_dto · domain_objects_test.