API Reference
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(): IntRaw 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());
}fn allocate(capacity: Int): ByteBufferAllocate 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));fn capacity(): IntAllocated 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): ByteBufferCopy 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));fn fromArray(bytes: Array<Int>): ByteBufferBuild 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));fn fromString(str: String): ByteBufferBuild 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()));fn length(): IntLive 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): Float32-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): Float32-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));fn readFloat64BE(offset: Int): Float64-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));fn readFloat64LE(offset: Int): Float64-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): IntSigned 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): IntSigned 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): IntSigned 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): IntSigned 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): IntSigned 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): IntSigned 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));fn readInt8(offset: Int): IntSigned 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));fn readString(offset: Int, length: Int): StringDecode 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));fn readUInt16BE(offset: Int): IntUnsigned 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));fn readUInt16LE(offset: Int): IntUnsigned 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): IntUnsigned 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): IntUnsigned 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));fn readUInt8(offset: Int): IntUnsigned 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): ByteBufferCopy 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));fn writeFloat32BE(offset: Int, val: Float): VoidStore 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): VoidStore 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): VoidStore 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): VoidStore 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): VoidStore 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): VoidStore 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): VoidStore 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): VoidStore 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): VoidStore 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): VoidStore 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): VoidStore 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));fn writeString(offset: Int, str: String): IntEncode 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));fn writeUInt16BE(offset: Int, val: Int): VoidStore 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): VoidStore 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): VoidStore 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): VoidStore 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): VoidStore 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