Bytes (byte, [byte])
Luma provides two byte-oriented types: a single byte value and [byte] byte lists.
Together they cover binary I/O, protocol parsing, encoding, and low-level data manipulation.
The byte type
A byte is a single 8-bit unsigned value (0–255).
It can be written as a decimal or hexadecimal literal.
b: byte = 65
b = 0x41 // same valueByte values are commonly used for protocol commands, status codes, and delimiters.
if command == 0xFF {
print("error packet")
}byte to int
b: byte = 0x41
n: int = b.to_int() // 65Direct numeric cast, not a string parse.
The [byte] type
A [byte] is an ordered list of bytes — Luma’s type for raw binary data.
data: [byte] = [0x48, 0x65, 0x6C, 0x6C, 0x6F]Byte lists are used for file contents, network packets, cryptographic output, and any data that isn’t text.
Methods summary
byte methods
| Method | Returns | Description |
|---|---|---|
b.to_int() |
int |
Numeric value (0–255) |
[byte] methods
| Method | Returns | Description |
|---|---|---|
data.to_str() |
str |
Decode as UTF-8 string |
data.to_hex() |
str |
Lowercase hex, 2 chars per byte |
data.to_base64() |
str |
Standard base64 with padding (RFC 4648) |
data.to_base64url() |
str |
URL-safe base64, no padding |
data.to_int16() |
int |
Decode 2 bytes, little-endian |
data.to_int24() |
int |
Decode 3 bytes, little-endian |
data.to_int32() |
int |
Decode 4 bytes, little-endian |
data.to_int48() |
int |
Decode 6 bytes, little-endian |
data.to_int64() |
int |
Decode 8 bytes, little-endian |
data.to_int16_be() |
int |
Decode 2 bytes, big-endian |
data.to_int24_be() |
int |
Decode 3 bytes, big-endian |
data.to_int32_be() |
int |
Decode 4 bytes, big-endian |
data.to_int48_be() |
int |
Decode 6 bytes, big-endian |
data.to_int64_be() |
int |
Decode 8 bytes, big-endian |
data.to_float64_be() |
float |
Decode 8 bytes, big-endian IEEE 754 float |
data.len() |
int |
Number of bytes |
data.add(b) |
— | Append a byte |
data.add_all(other) |
— | Append another byte list |
data.remove(b) |
bool |
Remove first occurrence |
data.remove_all(vals) |
— | Remove all occurrences |
data.sort() |
— | Sort in place |
data.filter(fn) |
[byte] |
Filter with predicate |
data.intersect(other) |
[byte] |
Elements common to both |
int to bytes
| Method | Returns | Description |
|---|---|---|
n.to_byte() |
[byte] |
Single-byte list (lowest 8 bits) |
n.to_int16() |
[byte] |
2-byte little-endian |
n.to_int24() |
[byte] |
3-byte little-endian |
n.to_int32() |
[byte] |
4-byte little-endian |
n.to_int48() |
[byte] |
6-byte little-endian |
n.to_int64() |
[byte] |
8-byte little-endian |
n.to_int16_be() |
[byte] |
2-byte big-endian |
n.to_int24_be() |
[byte] |
3-byte big-endian |
n.to_int32_be() |
[byte] |
4-byte big-endian |
n.to_int48_be() |
[byte] |
6-byte big-endian |
n.to_int64_be() |
[byte] |
8-byte big-endian |
str to bytes
| Method | Returns | Description |
|---|---|---|
s.to_bytes() |
[byte] |
UTF-8 encode string |
s.from_hex() |
[byte] or ?[byte] |
Decode hex string |
s.from_base64() |
[byte] or ?[byte] |
Decode base64 string |
s.from_base64url() |
[byte] or ?[byte] |
Decode URL-safe base64 string |
Creating byte lists
From literals
header: [byte] = [0x00, 0x01, 0x02, 0x03]
ascii: [byte] = [72, 101, 108, 108, 111]
empty: [byte] = []From strings
data: [byte] = "Hello".to_bytes() // [72, 101, 108, 108, 111]From hex or base64
data: [byte] = "48656c6c6f".from_hex() // [72, 101, 108, 108, 111]
data = "SGVsbG8=".from_base64() // same bytes
data = "SGVsbG8".from_base64url() // same bytesFrom integers
bytes: [byte] = 0x1234.to_int16() // [0x34, 0x12]
bytes = 5.to_int32() // [0x05, 0x00, 0x00, 0x00]
bytes = 65.to_byte() // [0x41]From files
data: [byte] = file("image.png").read()
chunk: [byte] = file("data.db").read_at(4096, 512)Encoding bytes as text
Byte lists can be encoded as text strings for display, transport, or storage.
data: [byte] = [0x48, 0x65, 0x6C, 0x6C, 0x6F]
text: str = data.to_str() // "Hello"
hex: str = data.to_hex() // "48656c6c6f"
b64: str = data.to_base64() // "SGVsbG8="
b64url: str = data.to_base64url() // "SGVsbG8"| Method | Format |
|---|---|
to_str() |
UTF-8 string — inverse of str.to_bytes() |
to_hex() |
Lowercase hex, 2 chars per byte |
to_base64() |
Standard base64 with padding (RFC 4648) |
to_base64url() |
URL-safe base64, no padding (- and _ instead of + and /) |
Encoding always succeeds.
Decoding text to bytes
The from_* methods on str decode text back to bytes.
The declared return type controls error handling:
// Panics on invalid input:
data: [byte] = "48656c6c6f".from_hex()
// Returns nil on invalid input:
data: ?[byte] = user_input.from_hex()Use [byte] when you trust the input. Use ?[byte] when the input might be invalid.
// Hex
data: [byte] = "48656c6c6f".from_hex()
safe: ?[byte] = "not hex!!".from_hex() // nil
// Base64
data = "SGVsbG8=".from_base64()
safe = "bad base64".from_base64() // nil
// URL-safe Base64
data = "SGVsbG8".from_base64url()
safe = "!!!".from_base64url() // nilInteger conversion
The method name determines the width (16, 24, 32, or 48 bits). The _be suffix selects big-endian byte order; without it, little-endian is used.
Bytes to integer (little-endian)
data: [byte] = [0x78, 0x56, 0x34, 0x12]
value: int = data.to_int16() // 2 bytes → 0x5678 = 22136
value = data.to_int24() // 3 bytes → 0x345678 = 3430008
value = data.to_int32() // 4 bytes → 0x12345678 = 305419896Bytes to integer (big-endian)
data: [byte] = [0x12, 0x34, 0x56, 0x78]
value: int = data.to_int16_be() // 2 bytes → 0x1234 = 4660
value = data.to_int24_be() // 3 bytes → 0x123456 = 1193046
value = data.to_int32_be() // 4 bytes → 0x12345678 = 305419896Big-endian is what network protocols and binary file formats (SQLite, PNG, etc.) use — most significant byte first.
48-bit and 64-bit integers
data: [byte] = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06]
le: int = data.to_int48() // little-endian
be: int = data.to_int48_be() // big-endiandata: [byte] = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]
le: int = data.to_int64() // little-endian
be: int = data.to_int64_be() // big-endianFloat from bytes (big-endian)
data: [byte] = [0x40, 0x09, 0x21, 0xFB, 0x54, 0x44, 0x2D, 0x18]
pi: float = data.to_float64_be() // 3.141592653589793Decodes 8 bytes as a big-endian IEEE 754 double-precision float. Used by binary file formats like SQLite.
Integer to bytes (little-endian)
n: int = 0x1234
bytes: [byte] = n.to_int16() // [0x34, 0x12]
bytes = n.to_int24() // [0x34, 0x12, 0x00]
bytes = n.to_int32() // [0x34, 0x12, 0x00, 0x00]
bytes = n.to_int48() // [0x34, 0x12, 0x00, 0x00, 0x00, 0x00]
bytes = n.to_int64() // [0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]Integer to bytes (big-endian)
n: int = 0x1234
bytes: [byte] = n.to_int16_be() // [0x12, 0x34]
bytes = n.to_int24_be() // [0x00, 0x12, 0x34]
bytes = n.to_int32_be() // [0x00, 0x00, 0x12, 0x34]
bytes = n.to_int48_be() // [0x00, 0x00, 0x00, 0x00, 0x12, 0x34]
bytes = n.to_int64_be() // [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x34]These are inverses: n.to_int32().to_int32() and n.to_int32_be().to_int32_be() both return the original value.
Single byte
b: [byte] = 65.to_byte() // [0x41]
b = 256.to_byte() // [0x00] — only lowest 8 bitsList operations
Byte lists support the same operations as all other Luma lists.
data: [byte] = [0x01, 0x02, 0x03]
data.add(0x04) // [0x01, 0x02, 0x03, 0x04]
data.add_all([0x05, 0x06]) // [0x01, 0x02, 0x03, 0x04, 0x05, 0x06]
data.remove(0x02) // true, removes first 0x02
data.sort() // sorts bytes numerically
print(data.len()) // number of bytes
filtered: [byte] = data.filter(b -> b > 0x03)Index access and slicing work as expected:
data: [byte] = [0x10, 0x20, 0x30, 0x40, 0x50]
print(data[0]) // 16 (0x10)
slice: [byte] = data[1..4] // [0x20, 0x30, 0x40]
tail: [byte] = data[3..] // [0x40, 0x50]File I/O
Reading bytes from files
// Read entire file as bytes
data: [byte] = file("image.png").read()
// Read with optional return (nil if file missing)
data: ?[byte] = file("image.png").read()
// Read bytes at specific offset
chunk: [byte] = file("data.db").read_at(4096, 512)Writing bytes to files
// Write bytes (replaces file)
file("output.bin").write(data)
// Write bytes at offset (no truncation)
file("data.db").write_at(0, header_bytes)See File I/O for the full file API.
Working with buffer
The buffer type provides cursor-based binary reading and writing. It works directly with bytes.
// Build a packet
buf: buffer = buffer()
buf.write(0x03) // single byte
buf.write(data) // byte list
buf.write("SELECT 1") // string as UTF-8 bytes
packet: [byte] = buf.to_bytes()
// Parse a packet
buf = buffer(packet)
command: byte = buf.read() // one byte
payload: [byte] = buf.read(8) // 8 bytes
text: str = buf.read(0x00) // read until null terminatorSee Buffer for the full buffer API.
Parsing bytes from strings
A string can be parsed as a single byte value using to_byte():
b: ?byte = "0x48".to_byte() // 72
b = "72".to_byte() // 72
b = "invalid".to_byte() // nil
b2: byte = "FF".to_byte(0x00) // 0 (fallback on failure)Supports hex (0x prefix) and decimal formats.
Examples
Hex round-trip
original: str = "Hello"
hex: str = original.to_bytes().to_hex() // "48656c6c6f"
restored: str = hex.from_hex().to_str() // "Hello"Binary protocol header
// Encode a 3-byte length + 1-byte sequence ID
body: [byte] = [0x03, 0x53, 0x45, 0x4C]
header: [byte] = body.len().to_int24()
buf: buffer = buffer()
buf.write(header) // 3-byte length
buf.write(0x00) // sequence ID
buf.write(body) // payload
sock.write(buf.to_bytes())File hash as hex
content: [byte] = file("document.pdf").read()
hash: [byte] = crypto.sha256(content)
print(hash.to_hex())URL-safe token
token: str = crypto.rand(32).to_base64url()
print(token)Patch bytes in a binary file
f: file = file("record.dat")
f.write_at(0, 0x1234.to_int16()) // write 2-byte header
chunk: [byte] = f.read_at(0, 2)
print(chunk.to_int16()) // 0x1234Design notes
byteis a single value;[byte]is a list — there is no separate “byte array” type[byte]supports all standard list operations (add,remove,sort,filter, etc.)- Encoding methods (
to_hex,to_base64,to_base64url) always succeed - Decoding methods (
from_hex,from_base64,from_base64url) use type-based dispatch for error handling:[byte]panics,?[byte]returns nil - Multi-byte integer conversions default to little-endian; the
_besuffix selects big-endian - Available widths: 16-bit (2 bytes), 24-bit (3 bytes), 32-bit (4 bytes), 48-bit (6 bytes), 64-bit (8 bytes)
- Float conversion:
to_float64_be()decodes 8-byte big-endian IEEE 754 floats - Integer byte-order conversion is a method on
intand[byte], not on buffer — each concern owns its own logic - No implicit coercion between
byte,int, andstr— all conversions are explicit