@std/bytes

API Reference

class ByteBuffer

Fixed-size raw byte buffer. All offsets and sizes are plain `Int`; integer lanes widen into 64-bit `Int` (signed lanes sign-extend, unsigned lanes zero-extend) and float lanes widen into 64-bit `Float`. Any access past the buffer length aborts with an out-of-bounds error. A buffer never grows or shrinks, so `length()` and `capacity()` are the same number and the address from `address()` stays put for the life of the buffer. Build one with `allocate` for raw space, `fromString` for text, or `fromArray` for a list of byte values. Reads come in a signed and an unsigned flavour per width. `readInt8` sign-extends, so `0xFF` comes back as -1, while `readUInt8` zero-extends and gives 255. The `write*` methods have no such split: they all store the low bits of the value, so `writeInt8` and `writeUInt8` are the same operation and differ only in what a later read makes of the byte.

init(handle: Int)

fields

  • handle: Int
fn address(): Int

Raw address of the first byte, for `Pointer.fromAddress` inside `unsafe` blocks. The buffer must outlive every pointer made from it, and writes must stay within `capacity()` so the backing store never moves. Nothing here checks the type you pick for the pointer, so `Pointer.fromAddress<Int>` over a 2-byte buffer reads 8 bytes and walks off the end. Size the buffer for the element type first.

returns — address of byte 0, for `Pointer.fromAddress`.

import { ByteBuffer } from "@std/bytes";

let b = ByteBuffer.allocate(2);
unsafe {
let p = Pointer.fromAddress<Byte>(b.address());
p.write(9);
print(b.readUInt8(0), p.read());
}
Run in Playground
fn allocate(capacity: Int): ByteBuffer

Allocate a zero-filled buffer. Every byte starts as 0, and the size is final: the buffer never resizes, so `length()` and `capacity()` both report `capacity` for the rest of the buffer's life.

capacity — byte count; length and capacity both start here.

returns — buffer of `capacity` zero bytes.

import { ByteBuffer } from "@std/bytes";

let b = ByteBuffer.allocate(4);
print(b.length(), b.capacity(), b.readUInt8(3));
Run in Playground
fn capacity(): Int

Allocated byte count. There is no growth path, so this equals `length()`. It is the bound to check a raw pointer from `address()` against.

returns — number of allocated bytes.

fn copyWithin(target: Int, start: Int, end: Int): ByteBuffer

Copy bytes within this buffer (overlap-safe, like TypedArray.set). The source range is moved as a block, so it survives a target that overlaps it. Both the source range and the target range are checked before anything is written: a `end` past `length()`, or a `target` where `target + (end - start)` would pass `length()`, stops the program with `byte buffer out of bounds` and leaves the buffer untouched.

target — offset to write to.

start — first byte to read, inclusive.

end — one past the last byte to read.

returns — the same buffer, so calls can be chained.

import { ByteBuffer } from "@std/bytes";

let b = ByteBuffer.fromArray([0, 1, 2, 3, 4, 5]);
b.copyWithin(1, 0, 4);
print(b.readUInt8(0), b.readUInt8(1), b.readUInt8(4));
Run in Playground
fn fromArray(bytes: Array<Int>): ByteBuffer

Build a buffer from byte values (each masked to 8 bits). Each value is stored through the same path as `writeUInt8`, so only the low 8 bits survive: 300 becomes 44, and -1 becomes 255.

bytes — source bytes; length sets the buffer size.

returns — buffer holding the low 8 bits of every value.

import { ByteBuffer } from "@std/bytes";

let b = ByteBuffer.fromArray([0, 127, 128, 255]);
print(b.length(), b.readUInt8(2), b.readInt8(2));
Run in Playground
fn fromString(str: String): ByteBuffer

Build a buffer holding the UTF-8 bytes of a string. The size is the UTF-8 byte count of `str`, not `str.length()`: `length()` counts characters, and any character outside ASCII takes more bytes than it has characters. The buffer holds exactly the encoding with nothing spare, so writing past the end stops the program with `byte buffer out of bounds`.

str — source text; any text, encoded as UTF-8.

returns — buffer of the UTF-8 byte count of `str`.

import { ByteBuffer } from "@std/bytes";

let b = ByteBuffer.fromString("hi");
print(b.length(), b.readString(0, b.length()));
Run in Playground
fn length(): Int

Live byte count. A buffer has no resize, so this never changes after `allocate` and always equals `capacity()`.

returns — number of bytes in the buffer.

fn readFloat32BE(offset: Int): Float

32-bit float lane, big-endian, widened to `Float`.

offset — first of the four bytes. The lane has to fit inside the buffer, so an offset above `length() - 4` stops the program with `byte buffer out of bounds`.

returns — the four bytes as a 32-bit float, widened to 64 bits, so the value keeps about 7 decimal digits.

fn readFloat32LE(offset: Int): Float

32-bit float lane, little-endian, widened to `Float`.

offset — first of the four bytes. The lane has to fit inside the buffer, so an offset above `length() - 4` stops the program with `byte buffer out of bounds`.

returns — the four bytes as a 32-bit float, widened to 64 bits, so the value keeps about 7 decimal digits.

import { ByteBuffer } from "@std/bytes";

let b = ByteBuffer.allocate(4);
b.writeFloat32LE(0, 1.5);
print(b.readFloat32LE(0), b.readUInt8(0), b.readUInt8(3));
Run in Playground
fn readFloat64BE(offset: Int): Float

64-bit float lane, big-endian. The IEEE-754 layout puts the sign bit in the first byte and the low mantissa bits in the last, which is the reverse of the little-endian order.

offset — first of the eight bytes. The lane has to fit inside the buffer, so an offset above `length() - 8` stops the program with `byte buffer out of bounds`.

returns — the eight bytes as a 64-bit float, with no precision lost.

import { ByteBuffer } from "@std/bytes";

let b = ByteBuffer.allocate(8);
b.writeFloat64BE(0, 1.0);
print(b.readFloat64BE(0), b.readUInt8(0), b.readUInt8(7));
Run in Playground
fn readFloat64LE(offset: Int): Float

64-bit float lane, little-endian.

offset — first of the eight bytes. The lane has to fit inside the buffer, so an offset above `length() - 8` stops the program with `byte buffer out of bounds`.

returns — the eight bytes as a 64-bit float, with no precision lost.

fn readInt16BE(offset: Int): Int

Signed 16-bit lane, big-endian, sign-extended.

offset — first of the two bytes. The lane has to fit inside the buffer, so an offset above `length() - 2` stops the program with `byte buffer out of bounds`.

returns — the two bytes read high byte first, widened to a 64-bit `Int` and sign-extended from 16 bits.

fn readInt16LE(offset: Int): Int

Signed 16-bit lane, little-endian, sign-extended.

offset — first of the two bytes. The lane has to fit inside the buffer, so an offset above `length() - 2` stops the program with `byte buffer out of bounds`.

returns — the two bytes read low byte first, widened to a 64-bit `Int` and sign-extended from 16 bits.

fn readInt32BE(offset: Int): Int

Signed 32-bit lane, big-endian, sign-extended.

offset — first of the four bytes. The lane has to fit inside the buffer, so an offset above `length() - 4` stops the program with `byte buffer out of bounds`.

returns — the four bytes read high byte first, widened to a 64-bit `Int` and sign-extended from 32 bits.

fn readInt32LE(offset: Int): Int

Signed 32-bit lane, little-endian, sign-extended.

offset — first of the four bytes. The lane has to fit inside the buffer, so an offset above `length() - 4` stops the program with `byte buffer out of bounds`.

returns — the four bytes read low byte first, widened to a 64-bit `Int` and sign-extended from 32 bits.

fn readInt64BE(offset: Int): Int

Signed 64-bit lane, big-endian.

offset — first of the eight bytes. The lane has to fit inside the buffer, so an offset above `length() - 8` stops the program with `byte buffer out of bounds`.

returns — the eight bytes read high byte first, widened to a 64-bit `Int`.

fn readInt64LE(offset: Int): Int

Signed 64-bit lane, little-endian. There is no unsigned 64-bit accessor, because `Int` is already 64 bits wide and holds the full bit pattern either way. Use `readUInt32` when the field is known to be 32 bits and a non-negative value.

offset — first of the eight bytes. The lane has to fit inside the buffer, so an offset above `length() - 8` stops the program with `byte buffer out of bounds`.

returns — the eight bytes read low byte first, widened to a 64-bit `Int`.

import { ByteBuffer } from "@std/bytes";

let b = ByteBuffer.allocate(8);
b.writeInt64LE(0, -2);
print(b.readInt64LE(0), b.readUInt8(0), b.readUInt8(7));
Run in Playground
fn readInt8(offset: Int): Int

Signed 8-bit lane, sign-extended.

offset — byte to read; outside 0..length() stops the program with `byte buffer out of bounds`.

returns — the byte widened to a 64-bit `Int` with its top 56 bits filled from bit 7, so 0xFF reads back as -1.

import { ByteBuffer } from "@std/bytes";

let b = ByteBuffer.allocate(1);
b.writeUInt8(0, 255);
print(b.readInt8(0), b.readUInt8(0));
Run in Playground
fn readString(offset: Int, length: Int): String

Decode UTF-8 bytes as a string. The decode never fails: bytes that are not valid UTF-8 each become one U+FFFD replacement character, so a buffer of arbitrary bytes still produces a string, just not the original text. The returned string counts characters, while the buffer counts bytes, so a multi-byte string is longer than the range it came from.

offset — first byte.

length — byte count; the range has to fit inside the buffer or the program stops with `byte buffer out of bounds`.

returns — the decoded text.

import { ByteBuffer } from "@std/bytes";

let b = ByteBuffer.fromString("ok");
print(b.readString(0, 2));
Run in Playground
fn readUInt16BE(offset: Int): Int

Unsigned 16-bit lane, big-endian, zero-extended. The same bytes read with the two orders give two different numbers, so the suffix has to match the writer.

offset — first of the two bytes. The lane has to fit inside the buffer, so an offset above `length() - 2` stops the program with `byte buffer out of bounds`.

returns — the two bytes read high byte first, widened to a 64-bit `Int`, 0 through 65535.

import { ByteBuffer } from "@std/bytes";

let b = ByteBuffer.allocate(2);
b.writeUInt16BE(0, 0x1234);
print(b.readUInt8(0), b.readUInt8(1), b.readUInt16BE(0));
Run in Playground
fn readUInt16LE(offset: Int): Int

Unsigned 16-bit lane, little-endian, zero-extended.

offset — first of the two bytes. The lane has to fit inside the buffer, so an offset above `length() - 2` stops the program with `byte buffer out of bounds`.

returns — the two bytes read low byte first, widened to a 64-bit `Int`, 0 through 65535.

fn readUInt32BE(offset: Int): Int

Unsigned 32-bit lane, big-endian, zero-extended.

offset — first of the four bytes. The lane has to fit inside the buffer, so an offset above `length() - 4` stops the program with `byte buffer out of bounds`.

returns — the four bytes read high byte first, widened to a 64-bit `Int`, 0 through 4294967295.

fn readUInt32LE(offset: Int): Int

Unsigned 32-bit lane, little-endian, zero-extended. The signed and unsigned readers differ on the same bytes: 0xFFFFFFFF reads as 4294967295 here and as -1 through `readInt32LE`.

offset — first of the four bytes. The lane has to fit inside the buffer, so an offset above `length() - 4` stops the program with `byte buffer out of bounds`.

returns — the four bytes read low byte first, widened to a 64-bit `Int`, 0 through 4294967295.

import { ByteBuffer } from "@std/bytes";

let b = ByteBuffer.allocate(4);
b.writeInt32LE(0, -1);
print(b.readUInt32LE(0), b.readInt32LE(0));
Run in Playground
fn readUInt8(offset: Int): Int

Unsigned 8-bit lane, zero-extended.

offset — byte to read; outside 0..length() stops the program with `byte buffer out of bounds`.

returns — the byte widened to a 64-bit `Int`, 0 through 255.

fn slice(start: Int, end: Int): ByteBuffer

Copy a sub-range into a fresh buffer. The source is copied one byte at a time, so the two buffers share nothing afterwards. A `end` above `length()` or a `start` below 0 stops the program with `byte buffer out of bounds`, and a `end` below `start` sizes the result negative and stops it with `negative byte buffer capacity`. An empty range gives an empty buffer.

start — first byte, inclusive.

end — one past the last byte.

returns — new buffer holding bytes `start` through `end - 1`.

import { ByteBuffer } from "@std/bytes";

let b = ByteBuffer.fromArray([0, 1, 2, 3, 4]);
let mid = b.slice(1, 3);
print(mid.length(), mid.readUInt8(0), mid.readUInt8(1));
Run in Playground
fn writeFloat32BE(offset: Int, val: Float): Void

Store a `Float` narrowed to 32 bits, big-endian.

offset — first of the four bytes; the lane has to fit inside the buffer or the program stops with `byte buffer out of bounds`.

val — source value; narrowed to a 32-bit float, so 0.1 comes back from a read as 0.10000000149011612.

fn writeFloat32LE(offset: Int, val: Float): Void

Store a `Float` narrowed to 32 bits, little-endian.

offset — first of the four bytes; the lane has to fit inside the buffer or the program stops with `byte buffer out of bounds`.

val — source value; narrowed to a 32-bit float, so 0.1 comes back from a read as 0.10000000149011612. A value outside the 32-bit range becomes infinity. Identical to `writeFloat32BE` except for the byte order.

fn writeFloat64BE(offset: Int, val: Float): Void

Store 64 bits of float, big-endian.

offset — first of the eight bytes; the lane has to fit inside the buffer or the program stops with `byte buffer out of bounds`.

val — source value; all 64 bits are kept, so a round trip through the buffer is exact.

fn writeFloat64LE(offset: Int, val: Float): Void

Store 64 bits of float, little-endian.

offset — first of the eight bytes; the lane has to fit inside the buffer or the program stops with `byte buffer out of bounds`.

val — source value; all 64 bits are kept, so a round trip through the buffer is exact.

fn writeInt16BE(offset: Int, val: Int): Void

Store the low 16 bits, big-endian.

offset — first of the two bytes; the lane has to fit inside the buffer or the program stops with `byte buffer out of bounds`.

val — source value; only the low 16 bits are kept. Identical to `writeUInt16BE`.

fn writeInt16LE(offset: Int, val: Int): Void

Store the low 16 bits, little-endian.

offset — first of the two bytes; the lane has to fit inside the buffer or the program stops with `byte buffer out of bounds`.

val — source value; only the low 16 bits are kept. Identical to `writeUInt16LE`.

fn writeInt32BE(offset: Int, val: Int): Void

Store the low 32 bits, big-endian.

offset — first of the four bytes; the lane has to fit inside the buffer or the program stops with `byte buffer out of bounds`.

val — source value; only the low 32 bits are kept. Identical to `writeUInt32BE`.

fn writeInt32LE(offset: Int, val: Int): Void

Store the low 32 bits, little-endian.

offset — first of the four bytes; the lane has to fit inside the buffer or the program stops with `byte buffer out of bounds`.

val — source value; only the low 32 bits are kept. Identical to `writeUInt32LE`.

fn writeInt64BE(offset: Int, val: Int): Void

Store 64 bits, big-endian.

offset — first of the eight bytes; the lane has to fit inside the buffer or the program stops with `byte buffer out of bounds`.

val — source value; all 64 bits are kept.

fn writeInt64LE(offset: Int, val: Int): Void

Store 64 bits, little-endian.

offset — first of the eight bytes; the lane has to fit inside the buffer or the program stops with `byte buffer out of bounds`.

val — source value; all 64 bits are kept.

fn writeInt8(offset: Int, val: Int): Void

Store the low 8 bits.

offset — byte to write; outside 0..length() stops the program with `byte buffer out of bounds`.

val — source value; only the low 8 bits are kept, so 300 stores 44 and -1 stores 255. Identical to `writeUInt8`. A later `readInt8` sign-extends the byte and a later `readUInt8` does not.

import { ByteBuffer } from "@std/bytes";

let b = ByteBuffer.allocate(2);
b.writeInt8(0, 300);
b.writeUInt8(1, -1);
print(b.readUInt8(0), b.readUInt8(1));
Run in Playground
fn writeString(offset: Int, str: String): Int

Encode a string as UTF-8 bytes. The whole encoding has to fit, so a hand-sized buffer needs room for every byte of the encoding, not for every character of the string. Nothing past the string is touched, and no NUL terminator is appended: a C function that expects one needs a 0 byte stored at the offset after the text, with room left for it.

offset — first byte to write.

str — source text.

returns — bytes written, which is the UTF-8 byte count of `str`, not its character count.

import { ByteBuffer } from "@std/bytes";

let b = ByteBuffer.allocate(8);
let n = b.writeString(0, "hi");
print(n, b.readUInt8(0), b.readUInt8(1), b.readString(0, n));
Run in Playground
fn writeUInt16BE(offset: Int, val: Int): Void

Store the low 16 bits, big-endian.

offset — first of the two bytes; the lane has to fit inside the buffer or the program stops with `byte buffer out of bounds`.

val — source value; only the low 16 bits are kept. Identical to `writeInt16BE`.

fn writeUInt16LE(offset: Int, val: Int): Void

Store the low 16 bits, little-endian.

offset — first of the two bytes; the lane has to fit inside the buffer or the program stops with `byte buffer out of bounds`.

val — source value; only the low 16 bits are kept, so 70000 stores 4464. Identical to `writeInt16LE`.

fn writeUInt32BE(offset: Int, val: Int): Void

Store the low 32 bits, big-endian.

offset — first of the four bytes; the lane has to fit inside the buffer or the program stops with `byte buffer out of bounds`.

val — source value; only the low 32 bits are kept. Identical to `writeInt32BE`.

fn writeUInt32LE(offset: Int, val: Int): Void

Store the low 32 bits, little-endian.

offset — first of the four bytes; the lane has to fit inside the buffer or the program stops with `byte buffer out of bounds`.

val — source value; only the low 32 bits are kept, so a value above 4294967295 wraps into the low 32 bits. Identical to `writeInt32LE`.

fn writeUInt8(offset: Int, val: Int): Void

Store the low 8 bits.

offset — byte to write; outside 0..length() stops the program with `byte buffer out of bounds`.

val — source value; only the low 8 bits are kept, so 300 stores 44 and -1 stores 255. Identical to `writeInt8`. Pick the reader that matches how the byte should be interpreted.

Functions

fn utf8Len(str: String): Int