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 value

Byte 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()      // 65

Direct 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 bytes

From 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()                 // nil

Integer 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 = 305419896

Bytes 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 = 305419896

Big-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-endian
data: [byte] = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]

le: int = data.to_int64()          // little-endian
be: int = data.to_int64_be()       // big-endian

Float from bytes (big-endian)

data: [byte] = [0x40, 0x09, 0x21, 0xFB, 0x54, 0x44, 0x2D, 0x18]
pi: float = data.to_float64_be()   // 3.141592653589793

Decodes 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 bits

List 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 terminator

See 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())                    // 0x1234

Design notes

  • byte is 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 _be suffix 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 int and [byte], not on buffer — each concern owns its own logic
  • No implicit coercion between byte, int, and str — all conversions are explicit
Last updated on