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

Value Buffer Protocol

Records, rich enums, optionals, lists, maps, and error payloads cross the C ABI by value, serialized in a compact binary format called the WeaveFFI value buffer. A buffered value occupies exactly one (const uint8_t*, size_t) slot pair at the ABI, no matter how deeply it nests. This page is the normative wire-format and ownership specification every generator implements.

Which types are buffered

A type is buffered when it is one of:

  • a record (struct)
  • a rich (algebraic) enum
  • [T] (list)
  • {K: V} (map)
  • T? (optional), except Interface?

Everything else keeps its scalar or pointer ABI: integers, floats, and bool pass by value; strings pass as C strings; bytes as a raw pointer plus length; interfaces and iterators as opaque object pointers; Interface? as a nullable object pointer; C-style enums as int32_t.

Interfaces, iterators, and borrowed views (str, bytes_view) never appear inside a buffered type; validation rejects them in buffered positions.

Encoding

All multi-byte values are little-endian. There is no padding and no alignment; values are packed back to back. Lengths and element counts are u32, capping any single string, byte buffer, or collection at 232 − 1 entries.

IDL typeEncoding
bool1 byte: 0 or 1
i8 / u81 byte
i16 / u162 bytes
i32 / u324 bytes
i64 / u648 bytes
f324 bytes (IEEE 754 bits)
f648 bytes (IEEE 754 bits)
enum (C-style)i32 discriminant
handle / handle<T>u64
stringu32 byte length, then UTF-8 bytes (no terminator)
bytesu32 length, then raw bytes
T?1 flag byte (0 absent, 1 present), then the value when present
[T]u32 count, then each element
{K: V}u32 count, then alternating key, value
recordeach field in declaration order
rich enumi32 tag, then the active variant’s fields in order
error payloadthe matched code’s fields in declaration order

Because the format is compositional, arbitrary nesting ({string: [T?]}, records containing records, lists of rich enums, and so on) works with no per-shape special cases.

Note that [u8] canonicalizes to bytes at parse time: the two encode identically inside a buffer (a u32 count followed by the raw bytes), and canonicalizing keeps the top-level ABI consistent with Rust producers, where Vec<u8> maps to bytes.

A decoder must reject a buffer that is exhausted mid-value, holds an invalid bool or optional flag byte, holds invalid UTF-8 in a string, declares a length prefix larger than the bytes remaining, or leaves trailing bytes after the complete value. A malformed buffer is a producer/consumer contract violation (both sides are generated from one IDL), so consumers surface it through the same channel as a producer panic, not as a typed domain error.

ABI slots and ownership

Parameters. A buffered parameter named v lowers to two slots:

const uint8_t* v_ptr, size_t v_len

The caller owns the encoding and keeps it alive for the duration of the call; the callee decodes and never frees it.

Returns. A buffered return lowers exactly like a bytes return: the producer allocates the encoding and hands it back as the return value plus a trailing out-parameter:

const uint8_t* f(..., size_t* out_len, weaveffi_error* out_err);

The consumer decodes the buffer and then releases it with weaveffi_free_bytes(ptr, len).

Iterator elements. _next writes a producer-allocated buffer through const uint8_t** out_item plus size_t* out_len; the consumer decodes and frees it with weaveffi_free_bytes per element.

Async results. The completion callback receives an owned (const uint8_t* result_ptr, size_t result_len) pair: the consumer decodes (or copies) the encoding and then releases it with weaveffi_free_bytes. Owned interface results transfer ownership the same way: the callback adopts the object pointer.

Callback and listener arguments. Buffered callback arguments are borrowed (ptr, len) pairs valid only for the duration of the dispatch.

Structured errors

weaveffi_error carries a payload alongside the (code, message) pair:

typedef struct weaveffi_error {
    int32_t code;
    const char* message;
    const uint8_t* payload_ptr;
    size_t payload_len;
} weaveffi_error;

When an error domain code declares fields, a matching error’s payload_ptr/payload_len hold those fields serialized in the value-buffer format, in declaration order; payload_ptr is null when the code declares no fields. weaveffi_error_clear releases both the message and the payload. Generators decode the payload into properties of the raised exception (or returned error value) keyed by the field names.

What generators emit

Each language binding ships a small private runtime with a buffer writer and reader implementing the table above, plus one pack and one unpack routine per record and rich enum (generated from the IDL, so field order is fixed at generation time). Optionals, lists, and maps are handled generically by the writer/reader, recursing through element types. Records and rich enums map to idiomatic value types in the target language (data classes, structs, sealed class hierarchies); no handle wrapping, no destructors, no builders.