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.2

Luma 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)       // 0

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

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

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

Big-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 → 0x010203040506
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

[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.141592653589793

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

Examples

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()    // 4096

Hex 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 type
  • byte.to_int() is a direct numeric cast, not a string parse
  • to_bytes() and [byte].to_str() are UTF-8 conversions
  • from_* 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 _be suffix 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
Last updated on