Buffer Sizing Guide
Every protocol buffer must be sized using the generated EncodedLength API.
Never guess with vec![0u8; 4096].
Const-sized messages — stack array, no heap:
#![allow(unused)] fn main() { let mut buf = [0u8; HeartbeatEncoder::compute_length_with_header()]; }
Dynamic / ragged messages — compute exact size first with *EncodedLength,
then encode into a claim or slot of that exact length:
#![allow(unused)] fn main() { pub fn demo_car_size_and_encode() -> Result<Vec<u8>, Box<dyn std::error::Error>> { // Fuel: 2 entries with usage ASCII lengths 5 and 7. // Performance: 1 entry with 2 nested acceleration rows (fixed-only entries). // Message var-data: manufacturer / model / activationCode lengths. let complete_len = CarEncoder::compute_length() .fuel_figures_ragged(2, |ff| { ff.add()?.usage_description(5)?; // "Urban" ff.add()?.usage_description(7)?; // "Highway" Ok(()) })? .performance_figures_ragged(1, |pf| { pf.add()?.acceleration(|acc| { acc.uniform(2)?; Ok(()) })?; Ok(()) })? .manufacturer(5)? // "Honda" .model(9)? // "Civic VTi" .activation_code(6)? // "abcdef" .encoded_length_with_header(); // Exact size from compute_length → stack pad (this demo fits well under 512). const CAR_PAD: usize = 512; assert!( complete_len <= CAR_PAD, "sample car length {complete_len} exceeds stack pad {CAR_PAD}" ); let mut storage = [0u8; CAR_PAD]; let written = encode_sample_car(&mut storage[..complete_len])?; assert_eq!( written, complete_len, "CarEncodedLength must equal encoder-produced length" ); Ok(storage[..written].to_vec()) } /// Encode the canonical sample car into `buf` (must be pre-sized). 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) } }
(This code comes from the sbe-feature-tour sample crate.)
Key rules:
compute_length_with_header()isconstwhen the message has no var-data fields or groups — use it directly for stack array sizes.- For messages with groups or var-data, use the staged
*EncodedLengthbuilder. - Assert computed length equals actual encoded length after writing.
- Oversize
vec![0u8; 4096]"guess" buffers hide size bugs — avoid them.