Skip to content

Bytes

API summary (generated from the Beans source by npm run coverage): 1 type · 4 static methods · 30 instance methods.

Bytes is a move-only, growable buffer of raw bytes. It implements Send, so one owner can move it to another thread, but it is not Sync and cannot be shared for concurrent mutation. Use it to build binary data, read fixed-width integers out of a buffer, or collect text before turning it into a string.

Unlike string, a Bytes value can change in place. Methods that change it return unit; call them as statements.

Bytes is a native builtin with no .b source, reached through the runtime ABI table in src/expression.b. Its signatures are positional: the type in each slot is fixed, the names are not.

Construct a fresh buffer with new Bytes(n), which gives n zeroed bytes and panics on a negative n. Four statics build or measure buffers:

Bytes.from(string) -> Bytes
Bytes.from_raw(RawPtr<u8>, int) -> Bytes
Bytes.filled(int, int) -> Bytes
Bytes.uvarint_size(int) -> int
  • Bytes.from(s) returns a new buffer holding a copy of string s’s bytes.
  • Bytes.from_raw(pointer, len) copies len bytes from a raw pointer without taking ownership. It requires unsafe; a null pointer is accepted only when len is zero.
  • Bytes.filled(length, value) creates length bytes with every byte set to value. It is useful for a reusable socket read buffer.
  • Bytes.uvarint_size(v) returns how many bytes v would take as an unsigned varint, without writing anything.
let buf: Bytes = new Bytes(0)
let text: Bytes = Bytes.from("hello")
Bytes.len() -> int
Bytes.as_ptr() -> RawPtr<u8>
Bytes.reserve(int)
Bytes.resize(int)
Bytes.fill(int)
Bytes.get(int) -> Option<int>
Bytes.set(int, int)
Bytes.push(int)
Bytes.get_u8(int) -> int
Bytes.get_u16(int) -> int
Bytes.get_u32(int) -> int
Bytes.get_u64(int) -> int
Bytes.get_i64(int) -> int
Bytes.put_u8(int, int)
Bytes.put_u16(int, int)
Bytes.put_u32(int, int)
Bytes.put_u64(int, int)
Bytes.put_i64(int, int)
Bytes.slice(int, int) -> Bytes
Bytes.copy_from(Bytes, int)
Bytes.append(Bytes)
Bytes.append_string(string)
Bytes.append_int_text(int)
Bytes.append_i64(int)
Bytes.append_range(Bytes, int, int)
Bytes.to_string() -> string
Bytes.to_string_until_nul() -> string
Bytes.append_uvarint(int)
Bytes.get_uvarint(int) -> int
Bytes.crc32(int, int) -> int

bytes[i] is a checked, required read: it returns int and panics out of range. bytes[i] = value replaces an existing byte, like set(i, value). Neither a read nor an indexed write grows the buffer; use push to append. Compound bracket assignment is not supported.

  • len() is the number of bytes.
  • as_ptr() borrows the buffer’s raw pointer and requires unsafe. The pointer is null for an empty buffer. Keep the Bytes alive, do not free the pointer, and do not resize, reserve, append, or push while using it.
  • reserve(n) makes room for at least n bytes without changing the length.
  • resize(n) grows or shrinks the buffer to n bytes; new bytes read as zero.
  • fill(v) sets every existing byte to v.
  • get(i) returns some(byte) when present and none for a negative or out-of-range index. The byte is an int from 0 to 255.
  • To migrate a required get(i) read from 0.1.53, use bytes[i]. get_u8(i) also remains a required read and works with older compilers.
  • set(i, v) writes the byte at i, and panics if i is out of range.
  • push(v) adds one byte at the end, growing the buffer.

The get_* readers return a whole number read at a byte position, and the put_* writers write one at a position. All are little-endian and panic when the position plus the width runs past the end of the buffer.

  • slice(from, to) returns a new buffer with the bytes in [from, to).
  • copy_from(src, at) copies all of src’s bytes into this buffer starting at at.
  • append(other) adds another buffer’s bytes at the end; append_string(s) adds a string’s bytes; append_int_text(v) adds the decimal text form of an integer; append_i64(v) adds v as 8 little-endian bytes; and append_range(src, from, to) adds src’s bytes in [from, to).
  • to_string() returns every byte as a string, including any NUL bytes.
  • to_string_until_nul() stops at the first NUL byte.

A varint is a compact way to store an integer using fewer bytes for small values. Beans uses unsigned LEB128 over the full 64-bit pattern, so a negative value takes 10 bytes.

  • append_uvarint(v) adds v as an unsigned varint.
  • get_uvarint(pos) reads an unsigned varint that starts at pos. Advance your own position by Bytes.uvarint_size(v) to read the next one.
  • crc32(from, to) returns the IEEE CRC-32 checksum of the bytes in [from, to).

== and != compare two Bytes by value: same length and same bytes.

Build a record, then read it back:

import std.io
fn main() {
let buf: Bytes = new Bytes(0)
buf.append_string("id=")
buf.append_i64(42)
buf.push(10)
io.println("{buf.len()} bytes")
let header: Bytes = new Bytes(8)
header.put_u32(0, 65535)
header.put_u32(4, 7)
io.println("{header.get_u32(0)} {header.get_u32(4)}")
let text: Bytes = Bytes.from("hello")
io.println(text.to_string())
}

Write a run of varints, then walk them back out with uvarint_size:

import std.io
fn main() {
var rec: Bytes = new Bytes(0)
rec.append_uvarint(1)
rec.append_uvarint(300)
rec.append_uvarint(70000)
var pos: int = 0
var seen: List<int> = []
for seen.len() < 3 {
let v: int = rec.get_uvarint(pos)
seen.push(v)
pos = pos + Bytes.uvarint_size(v)
}
io.println(seen)
let check: Bytes = Bytes.from("123456789")
io.println("crc32 {check.crc32(0, check.len())}")
}