Skip to content

protobuf

stable

Encodes 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, …}
22 functions Data Formats
/ filter jk navigate Esc clear
Functions (22)
  1. encode_varint Encode an integer as a varint byte buffer
  2. decode_varint Decode varint bytes to value and length
  3. encode_string_field Encode a string field (wire type 2)
  4. encode_int_field Encode an integer field (wire type 0)
  5. encode_bytes_field Encode a bytes field (wire type 2)
  6. decode_field Decode one field from a byte buffer
  7. wire_type_name Get human-readable wire type name
  8. encode_message Encode a full message from a field table
  9. decode_message Decode all fields from a byte buffer
  10. encode_repeated Encode repeated varint fields
  11. encode_packed Encode packed repeated varints
  12. encode_float_field Encode a float32 field (wire type 5)
  13. encode_double_field Encode a float64 field (wire type 1)
  14. encode_bool_field Encode a bool field (wire type 0)
  15. encode_fixed32 Encode a fixed 32-bit field
  16. encode_fixed64 Encode a fixed 64-bit field
  17. zigzag_encode ZigZag-encode a signed integer
  18. zigzag_decode ZigZag-decode an unsigned integer
  19. encode_sint_field Encode a sint field (zigzag + varint)
  20. encode_submessage_field Wrap encoded bytes as a submessage field
  21. concat_bytes Concatenate multiple byte buffers
  22. 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, "alice@example.com")
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")
enespt-br