Type Conversion
Luma provides a complete set of explicit conversion methods. Every conversion uses method syntax (value.to_int()), never implicit coercion.
String-to-primitive conversions come in two forms: nullable (returns nil on failure) and fallback (returns a default). Choose nullable when you need to detect bad input; choose fallback when you always need a valid value.
Quick Reference
String → Primitive
| Method | Returns | Description |
|---|---|---|
s.to_int() |
?int |
Parse integer, nil on failure |
s.to_int(default) |
int |
Parse integer, fallback on failure |
s.to_float() |
?float |
Parse float, nil on failure |
s.to_float(default) |
float |
Parse float, fallback on failure |
s.to_bool() |
?bool |
Parse boolean, nil on failure |
s.to_bool(default) |
bool |
Parse boolean, fallback on failure |
s.to_byte() |
?byte |
Parse hex/decimal byte, nil on failure |
s.to_byte(default) |
byte |
Parse hex/decimal byte, fallback on failure |
Any → String
| Method | Returns | Description |
|---|---|---|
v.to_str() |
str |
Convert any value to its string representation (always succeeds) |
String ↔ Bytes
| Method | Returns | Description |
|---|---|---|
s.to_bytes() |
[byte] |
UTF-8 encode a string to bytes |
data.to_str() |
str |
Decode a [byte] list as UTF-8 string |
Byte ↔ Integer
| Method | Returns | Description |
|---|---|---|
b.to_int() |
int |
Convert a byte to its integer value (0–255) |
n.to_byte() |
[byte] |
Convert an int to a single-byte list (lowest 8 bits) |
Multi-byte Integer ↔ Bytes
| Method | Receiver | Returns | Description |
|---|---|---|---|
data.to_int16() |
[byte] |
int |
Decode 2 bytes, little-endian |
data.to_int24() |
[byte] |
int |
Decode 3 bytes, little-endian |
data.to_int32() |
[byte] |
int |
Decode 4 bytes, little-endian |
data.to_int48() |
[byte] |
int |
Decode 6 bytes, little-endian |
data.to_int64() |
[byte] |
int |
Decode 8 bytes, little-endian |
data.to_int16_be() |
[byte] |
int |
Decode 2 bytes, big-endian |
data.to_int24_be() |
[byte] |
int |
Decode 3 bytes, big-endian |
data.to_int32_be() |
[byte] |
int |
Decode 4 bytes, big-endian |
data.to_int48_be() |
[byte] |
int |
Decode 6 bytes, big-endian |
data.to_int64_be() |
[byte] |
int |
Decode 8 bytes, big-endian |
data.to_float64_be() |
[byte] |
float |
Decode 8 bytes, big-endian IEEE 754 float |
n.to_int16() |
int |
[byte] |
Encode as 2-byte little-endian |
n.to_int24() |
int |
[byte] |
Encode as 3-byte little-endian |
n.to_int32() |
int |
[byte] |
Encode as 4-byte little-endian |
n.to_int48() |
int |
[byte] |
Encode as 6-byte little-endian |
n.to_int64() |
int |
[byte] |
Encode as 8-byte little-endian |
n.to_int16_be() |
int |
[byte] |
Encode as 2-byte big-endian |
n.to_int24_be() |
int |
[byte] |
Encode as 3-byte big-endian |
n.to_int32_be() |
int |
[byte] |
Encode as 4-byte big-endian |
n.to_int48_be() |
int |
[byte] |
Encode as 6-byte big-endian |
n.to_int64_be() |
int |
[byte] |
Encode as 8-byte big-endian |
Encoding (Bytes ↔ Text)
| Method | Receiver | Returns | Description |
|---|---|---|---|
data.to_hex() |
[byte] |
str |
Lowercase hex, 2 chars per byte |
data.to_base64() |
[byte] |
str |
Standard base64 with padding (RFC 4648) |
data.to_base64url() |
[byte] |
str |
URL-safe base64, no padding |
s.from_hex() |
str |
[byte] |
Decode hex string (panics on invalid) |
s.from_hex() |
str |
?[byte] |
Decode hex string (nil on invalid) |
s.from_base64() |
str |
[byte] |
Decode base64 (panics on invalid) |
s.from_base64() |
str |
?[byte] |
Decode base64 (nil on invalid) |
s.from_base64url() |
str |
[byte] |
Decode URL-safe base64 (panics on invalid) |
s.from_base64url() |
str |
?[byte] |
Decode URL-safe base64 (nil on invalid) |
String → Primitive
Integer Conversion
name: str = "123"
x: ?int = name.to_int() // nullable — returns int or nil
y: int = name.to_int(0) // fallback — returns 0 on failure"42"→ 42"abc"→ nil" 7 "→ 7 (whitespace is trimmed)"-3"→ -3
Float Conversion
"3.14".to_float() // 3.14
"invalid".to_float() // nil
"5.2".to_float(0.0) // 5.2Luma uses the dot (.) as the decimal separator (no locale-dependent comma).
Boolean Conversion
"true".to_bool(false) // true
"FALSE".to_bool(true) // false
"0".to_bool(false) // false
"1".to_bool(false) // true
"maybe".to_bool() // nil- Case-insensitive
- Accepts
"true","false","1","0" - Anything else returns
nil(or the fallback default)
Byte Conversion
Parses a string into a single byte value (0–255). Supports hex and decimal formats:
"0x48".to_byte() // 72
"72".to_byte() // 72
"0XFF".to_byte() // 255
"invalid".to_byte() // nil
"999".to_byte() // nil (out of range)
"invalid".to_byte(0x00) // 0 (fallback)Nullable vs Fallback
Every string-to-primitive conversion supports two forms:
| Form | Return type | On failure |
|---|---|---|
s.to_int() |
?int |
returns nil |
s.to_int(0) |
int |
returns the default |
val: str = "xyz"
a: ?int = val.to_int() // nil
b: int = val.to_int(0) // 0
yes: bool = "TRUE".to_bool(false) // true
no: bool = "oops".to_bool(false) // false
x: ?byte = "ZZ".to_byte() // nil
y: byte = "ZZ".to_byte(0x00) // 0Use the nullable form when you need to distinguish “missing” from “zero”. Use the fallback form when you always need a valid value.
Any → String
age: int = 27
s: str = age.to_str() // "27"
pi: float = 3.14
s = pi.to_str() // "3.14"
flag: bool = true
s = flag.to_str() // "true"Works on any value, including optional or nil. Never fails.
String ↔ Bytes
String → Byte List
Convert a string to its raw UTF-8 bytes:
data: [byte] = "Hello".to_bytes()
// [72, 101, 108, 108, 111]Byte List → String
Convert bytes back to a UTF-8 string:
data: [byte] = [72, 101, 108, 108, 111]
text: str = data.to_str() // "Hello"These are inverses of each other: s.to_bytes().to_str() returns the original string.
Byte ↔ Integer
byte → int
A single byte value can be converted to int:
b: byte = 0x41
n: int = b.to_int() // 65Returns the numeric value (0–255). This is a direct cast, not a string parse.
int → [byte]
Convert an integer to a single-byte list (uses lowest 8 bits):
data: [byte] = 65.to_byte() // [0x41]
data = 256.to_byte() // [0x00] — only lowest 8 bitsMulti-byte Integer Conversions
Convert between integers and byte lists. The method name determines the width, and the _be suffix selects big-endian byte order (default is little-endian).
[byte] → int (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[byte] → int (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 file formats like SQLite 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 → 0x060504030201
be: int = data.to_int48_be() // big-endian → 0x010203040506data: [byte] = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]
le: int = data.to_int64() // little-endian
be: int = data.to_int64_be() // big-endian[byte] → float (big-endian)
Decode 8 bytes as a big-endian IEEE 754 double-precision float:
data: [byte] = [0x40, 0x09, 0x21, 0xFB, 0x54, 0x44, 0x2D, 0x18]
pi: float = data.to_float64_be() // 3.141592653589793Used by binary file formats like SQLite for storing REAL values.
int → [byte] (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]int → [byte] (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.
Encoding (Bytes ↔ Text)
Encode byte lists as text strings, and decode text strings back to bytes.
Encoding bytes to text
data: [byte] = [0x48, 0x65, 0x6C, 0x6C, 0x6F]
hex: str = data.to_hex() // "48656c6c6f"
b64: str = data.to_base64() // "SGVsbG8="
b64url: str = data.to_base64url() // "SGVsbG8"| Method | Format |
|---|---|
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 have two behaviors depending on the declared return type:
// 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 (hardcoded values, validated data). Use ?[byte] when the input might be invalid (user input, external data).
// 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() // nilExamples
Parsing user input
input: str = "42"
age: int = input.to_int(0)
if age < 1 {
print("invalid age")
}Safe number extraction
raw: str = " 3.14 "
val: ?float = raw.to_float()
match val {
nil -> print("not a number")
n -> print(n)
}Binary protocol (little-endian)
// Decode a 4-byte length header
header: [byte] = [0x05, 0x00, 0x00, 0x00]
length: int = header.to_int32() // 5
// Encode a length back
encoded: [byte] = length.to_int32()Binary file format (big-endian)
// Read a SQLite page size from file header (big-endian, bytes 16-17)
header: [byte] = file("test.db").read_at(16, 2)
page_size: int = header.to_int16_be() // 4096Hex round-trip
original: str = "Hello"
hex: str = original.to_bytes().to_hex() // "48656c6c6f"
restored: str = hex.from_hex().to_str() // "Hello"Notes
- All string-to-primitive conversions trim whitespace before parsing
to_str()never fails — it works on any typebyte.to_int()is a direct numeric cast, not a string parseto_bytes()and[byte].to_str()are UTF-8 conversionsfrom_*methods panic when the return type is[byte], and return nil when the return type is?[byte]- Multi-byte conversions default to little-endian byte order; use the
_besuffix for big-endian - Available integer 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 doubles - No auto-coercion (like JS or Python) — all conversions are explicit