protobuf
stableEncodes and decodes Protocol Buffer wire format at the byte level, providing field encoders for all scalar types plus message composition helpers.
use plugin protobuf::{encode_varint, decode_varint, encode_string_field, …} Functions (22)
- encode_varint Encode an integer as a varint byte buffer
- decode_varint Decode varint bytes to value and length
- encode_string_field Encode a string field (wire type 2)
- encode_int_field Encode an integer field (wire type 0)
- encode_bytes_field Encode a bytes field (wire type 2)
- decode_field Decode one field from a byte buffer
- wire_type_name Get human-readable wire type name
- encode_message Encode a full message from a field table
- decode_message Decode all fields from a byte buffer
- encode_repeated Encode repeated varint fields
- encode_packed Encode packed repeated varints
- encode_float_field Encode a float32 field (wire type 5)
- encode_double_field Encode a float64 field (wire type 1)
- encode_bool_field Encode a bool field (wire type 0)
- encode_fixed32 Encode a fixed 32-bit field
- encode_fixed64 Encode a fixed 64-bit field
- zigzag_encode ZigZag-encode a signed integer
- zigzag_decode ZigZag-decode an unsigned integer
- encode_sint_field Encode a sint field (zigzag + varint)
- encode_submessage_field Wrap encoded bytes as a submessage field
- concat_bytes Concatenate multiple byte buffers
- message_size Return byte length of an encoded message
Overview
protobuf is a low-level toolkit for hand-building and inspecting Protocol Buffer
wire-format payloads, working directly with bytes rather than generated message
classes. There is no schema compiler and no opaque message object: every encoder
returns a raw bytes buffer, and you compose a full message by encoding
individual fields and concatenating them (or by handing a descriptor table to
encode_message). Use it when you need to speak protobuf to a service without
pulling in a code generator — building a request by hand, parsing an unknown
payload field by field, or implementing length-prefixed framing.
The mental model is field-at-a-time: each encode_*_field helper writes one
tag-and-value record for a given field number, concat_bytes joins those records
into a message, and decode_field / decode_message walk a buffer back into
#{field_number, wire_type, value} entries. The zigzag_* helpers and
encode_sint_field cover signed-varint compaction, while encode_submessage_field
lets you nest a previously-encoded message as a single field.
Common patterns
Build a message field by field and concatenate the records:
use plugin protobuf::{encode_string_field, encode_int_field, encode_bool_field, concat_bytes, message_size}
let msg = concat_bytes(
encode_string_field(1, "Alice"),
encode_int_field(2, 30),
encode_bool_field(3, true)
)
print("encoded {message_size(msg)} bytes")
Or describe the whole message as a table and let encode_message do the work,
then read it back with decode_message:
use plugin protobuf::{encode_message, decode_message, wire_type_name}
let buf = encode_message([
#{"field_num": 1, "type": "string", "value": "Bob"},
#{"field_num": 2, "type": "sint", "value": -42}
])
for _, f in decode_message(buf) {
print("field {f["field_number"]} ({wire_type_name(f["wire_type"])}): {f["value"]}")
}
Nest an encoded message as a submessage field of an outer message:
use plugin protobuf::{encode_message, encode_submessage_field, encode_int_field, concat_bytes}
let inner = encode_message([#{"field_num": 1, "type": "string", "value": "nested"}])
let outer = concat_bytes(
encode_int_field(1, 7),
encode_submessage_field(2, inner)
)
Encode an integer as a varint byte buffer
Encodes an unsigned integer using the protobuf varint encoding. Use this for raw varint bytes without a field tag.
use plugin protobuf::{encode_varint}
let buf = encode_varint(300)
print(message_size(buf)) // 2 bytes
Decode varint bytes to value and length
Decodes the first varint from a byte buffer. Returns #{value: int, bytes_read: int}.
use plugin protobuf::{encode_varint, decode_varint}
let buf = encode_varint(150)
let result = decode_varint(buf)
print(result["value"]) // 150
print(result["bytes_read"]) // 2
Encode a string field (wire type 2)
Encodes a UTF-8 string as a length-delimited field (wire type 2).
use plugin protobuf::{encode_string_field, concat_bytes}
let name_field = encode_string_field(1, "Alice")
let email_field = encode_string_field(2, "[email protected]")
let msg = concat_bytes(name_field, email_field)
Encode an integer field (wire type 0)
Encodes an unsigned integer as a varint field (wire type 0). Use for int32, int64, uint32, uint64, and enum field types.
use plugin protobuf::{encode_int_field}
let age_field = encode_int_field(3, 30)
Encode a bytes field (wire type 2)
Encodes a raw byte buffer as a length-delimited field (wire type 2). Use for protobuf bytes fields.
use plugin protobuf::{encode_bytes_field}
let payload = encode_bytes_field(4, some_bytes)
Decode one field from a byte buffer
Decodes one field from the start of a byte buffer. Returns #{field_number, wire_type, value, bytes_read}. String-valid length-delimited data is decoded as a string; otherwise as bytes.
use plugin protobuf::{encode_string_field, decode_field}
let buf = encode_string_field(1, "hello")
let f = decode_field(buf)
print(f["field_number"]) // 1
print(f["value"]) // hello
Use bytes_read to advance through a buffer one field at a time:
use plugin protobuf::{encode_int_field, encode_string_field, concat_bytes, decode_field, wire_type_name}
let buf = concat_bytes(encode_int_field(1, 42), encode_string_field(2, "ok"))
let first = decode_field(buf)
print("first is {wire_type_name(first["wire_type"])} = {first["value"]}, read {first["bytes_read"]} bytes")
Get human-readable wire type name
Returns the human-readable name for a wire type integer (0–5).
use plugin protobuf::{wire_type_name}
print(wire_type_name(0)) // varint
print(wire_type_name(2)) // length-delimited
Encode a full message from a field table
Encodes a complete message from a table of field descriptor entries. Each entry must have field_num, type, and value. Supported types: "varint", "int", "sint", "bool", "string", "bytes", "float", "double", "fixed32", "fixed64".
use plugin protobuf::{encode_message}
let msg = encode_message([
#{"field_num": 1, "type": "string", "value": "Alice"},
#{"field_num": 2, "type": "int", "value": 30},
#{"field_num": 3, "type": "bool", "value": true}
])
Mix scalar kinds in one descriptor table — sint zigzag-encodes negatives and
double writes a fixed 8-byte value:
use plugin protobuf::{encode_message, message_size}
let reading = encode_message([
#{"field_num": 1, "type": "sint", "value": -7},
#{"field_num": 2, "type": "double", "value": 48.8566},
#{"field_num": 3, "type": "bytes", "value": some_bytes}
])
print("payload is {message_size(reading)} bytes")
Decode all fields from a byte buffer
Decodes all fields from a byte buffer. Returns a table where each entry has field_number, wire_type, and value.
use plugin protobuf::{encode_message, decode_message}
let buf = encode_message([#{"field_num": 1, "type": "string", "value": "Bob"}])
let fields = decode_message(buf)
for _, f in fields {
print("field {f["field_number"]}: {f["value"]}")
}
Encode repeated varint fields
Encodes multiple values for the same field number as separate varint records (non-packed repeated).
use plugin protobuf::{encode_repeated}
let ids = encode_repeated(5, [1, 2, 3, 4])
Encode packed repeated varints
Encodes multiple integers as a single length-delimited packed field. More efficient than encode_repeated for numeric arrays.
use plugin protobuf::{encode_packed}
let scores = encode_packed(6, [100, 200, 300])
The packed form is one length-delimited record regardless of element count, so it
is typically smaller than encode_repeated for the same values:
use plugin protobuf::{encode_repeated, encode_packed, message_size}
let values = [1, 2, 3, 4, 5]
print("repeated: {message_size(encode_repeated(6, values))} bytes")
print("packed: {message_size(encode_packed(6, values))} bytes")
Encode a float32 field (wire type 5)
Encodes a 32-bit float using wire type 5 (fixed 4 bytes).
use plugin protobuf::{encode_float_field}
let temp = encode_float_field(7, 36.6)
Encode a float64 field (wire type 1)
Encodes a 64-bit double using wire type 1 (fixed 8 bytes).
use plugin protobuf::{encode_double_field}
let lat = encode_double_field(8, 48.8566)
Encode a bool field (wire type 0)
Encodes a boolean as a varint field (0 or 1).
use plugin protobuf::{encode_bool_field}
let active = encode_bool_field(9, true)
Encode a fixed 32-bit field
Encodes a fixed-width unsigned 32-bit integer (always 4 bytes, wire type 5). Use for fixed32 and sfixed32 proto fields.
use plugin protobuf::{encode_fixed32}
let crc = encode_fixed32(10, 0xDEADBEEF)
Encode a fixed 64-bit field
Encodes a fixed-width 64-bit integer (always 8 bytes, wire type 1).
use plugin protobuf::{encode_fixed64}
let ts = encode_fixed64(11, 1700000000000)
ZigZag-encode a signed integer
Applies ZigZag encoding to a signed integer, producing a non-negative value suitable for varint encoding. Negative numbers encode more efficiently this way.
use plugin protobuf::{zigzag_encode, zigzag_decode}
print(zigzag_encode(-1)) // 1
print(zigzag_encode(1)) // 2
print(zigzag_decode(1)) // -1
ZigZag-decode an unsigned integer
Reverses ZigZag encoding, recovering the original signed integer.
use plugin protobuf::{zigzag_decode}
print(zigzag_decode(4)) // 2
Encode a sint field (zigzag + varint)
Encodes a signed integer using ZigZag encoding followed by varint (the sint32/sint64 wire format). More compact than encode_int_field for negative values.
use plugin protobuf::{encode_sint_field}
let delta = encode_sint_field(12, -100)
Wrap encoded bytes as a submessage field
Wraps an already-encoded message byte buffer as a length-delimited field, enabling nested messages.
use plugin protobuf::{encode_message, encode_submessage_field, concat_bytes}
let inner = encode_message([#{"field_num": 1, "type": "string", "value": "nested"}])
let outer = encode_submessage_field(1, inner)
Concatenate multiple byte buffers
Concatenates any number of byte buffers into one. All arguments must be bytes values.
use plugin protobuf::{encode_string_field, encode_int_field, concat_bytes}
let msg = concat_bytes(
encode_string_field(1, "Alice"),
encode_int_field(2, 42)
)
Return byte length of an encoded message
Returns the byte length of an encoded message buffer. Useful for logging or length-prefix framing.
use plugin protobuf::{encode_message, message_size}
let buf = encode_message([#{"field_num": 1, "type": "string", "value": "test"}])
print("encoded size: {message_size(buf)} bytes")