← Week 2: Custom Binary Protocols

Day 12: Zero-Copy Deserialization

Phase 3 · Jul 12, 2026

← Week 2: Custom Binary Protocols

Agenda (2–3 hours)

  • Read (45 min): bytes::Bytes documentation; rkyv README; FlatBuffers design documentation
  • Study (45 min): What does "zero-copy" mean precisely? At what layer does the copy happen with protobuf vs rkyv?
  • Practice (45 min): Benchmark JSON vs protobuf vs rkyv for a 10,000-element vector of structs: measure serialization, deserialization, and memory allocations
  • Challenge (30 min): When is zero-copy deserialization worth the added complexity? Identify a use case in Leo Secure Comms where it would matter
← Week 2: Custom Binary Protocols

The Copy Problem

Standard deserialization:

  1. Read bytes from TCP into a Vec<u8> (copy 1: network → kernel → userspace)
  2. Parse bytes into a Rust struct (copy 2: bytes → field-by-field allocation)

For hot paths handling millions of messages/second, copy 2 dominates CPU time.

Zero-copy: access data in its serialized form, directly in the receive buffer — no field-by-field allocation.

← Week 2: Custom Binary Protocols

bytes::Bytes

bytes::Bytes is a reference-counted byte slice with O(1) clone (no allocation):

let buf: Bytes = receive_buffer.freeze(); // zero-copy freeze of BytesMut

// Slice into sub-regions without copying
let key: Bytes = buf.slice(12..12 + key_len);
let value: Bytes = buf.slice(12 + key_len..);

// Pass to handler without copying
handler(key, value).await;

Crucial for network code: pass sub-slices of the receive buffer to handlers without copying. The reference count keeps the buffer alive until all slices are dropped.

← Week 2: Custom Binary Protocols

rkyv: Zero-Copy Rust Structs

use rkyv::{Archive, Serialize, Deserialize};

#[derive(Archive, Serialize, Deserialize)]
struct KvPair {
    key: String,
    value: Vec<u8>,
}

// Serialize: layout struct into byte buffer
let bytes = rkyv::to_bytes::<KvPair, 256>(&kv).unwrap();

// Zero-copy access: no allocation, direct memory access
let archived = unsafe { rkyv::archived_root::<KvPair>(&bytes) };
println!("{}", archived.key); // accesses bytes directly

rkyv works by writing the struct in a layout that matches memory layout — the "archived" struct is usable directly from the byte buffer.

← Week 2: Custom Binary Protocols

FlatBuffers vs Protocol Buffers

FlatBuffers Protocol Buffers
Zero-copy Yes (read without parsing) No (must decode to Rust struct)
Random access Yes (offset table) No (must scan from start)
Mutability No (read-only) Yes (mutable Rust structs)
Schema evolution Limited Excellent
Schema required Yes Yes
Overhead ~2× encoded size for table Compact varint encoding

FlatBuffers is used in gaming engines (Unity), low-latency databases, and Android.

← Week 2: Custom Binary Protocols

Key Takeaways

  • bytes::Bytes provides O(1) clone and slicing for network buffers — use it as the standard buffer type
  • rkyv enables zero-copy Rust struct access; best for read-heavy, allocation-sensitive hot paths
  • FlatBuffers offers random field access without parsing; tradeoff vs protobuf's compact encoding and better evolution support
  • Standard protobuf is fine for most services — zero-copy matters at >100k msg/s per core

Tomorrow: message authentication — signing frames to detect tampering.