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

Read and write binary packets

A Cursor keeps the byte position for a packet. Its typed operations read or write one complete field, with explicit byte order and no manual offset bookkeeping.

import std.bytes
import std.io
from std.encoding import Endian
from std.io import SeekFrom

def main() -> Result[(), AllocError | IoError | RangeError | std.bytes.BytesError | std.io.BinaryError | std.io.StreamError]:
    payload = b"hello"
    mut packet = std.io.cursor(std.bytes.empty()?)
    packet.write_u8(1u8)?
    # Check that a payload size fits its wire field before converting it.
    packet.write_u32(u32.try_from(len(payload))?, Endian.Big)?
    packet.write_f32(1.5f32, Endian.Big)?
    packet.write_bytes(&payload, 64)?
    drop(packet.seek(SeekFrom.Start(0))?)

    version = packet.read_u8()?
    length = i64(packet.read_u32(Endian.Big)?)
    scale = packet.read_f32(Endian.Big)?
    # A hostile length is rejected before allocating its owned payload copy.
    body = packet.read_bytes(length, 64)?
    packet.require_end()?
    print(version, scale, body.to_text()?, packet.position())?
    Ok(())
1 1.5 hello 14

The checked u32 conversion propagates RangeError if a payload length cannot fit the wire field. read_bytes returns an owned copy because borrowed views cannot escape a method. Scalar fields allocate nothing when storage already has capacity.

If a field is truncated, BinaryError.Truncated reports its position, requested width, and available bytes. The failed operation leaves the cursor in place; previous fields remain consumed. require_end detects unexpected trailing bytes. Raw stream reads and writes and seek use the same position, so they can be interleaved with typed fields when the format calls for them.