Bytes
Bytes module: raw byte manipulation.
Bytes wraps a String, which in the runtime is an array-backed buffer: march_string is {rc, tag, pad, len, data[]} — one allocation, an explicit length field, and a contiguous NUL-safe payload. Nothing here treats a String as text: string_byte_at, string_slice, string_chars and char_from_int are all byte-indexed in both backends.
This replaced Bytes(List(Int)), which cost one ~32-byte cons cell per byte and made length O(n), get O(i) and slice O(start+len). Measured on a 64 000-byte payload (interpreted): length 544 ms/call, get(last)
- See specs/plans/2026-08-10-array-backed-bytes-design.md.
The wrapper constructor is deliberately kept (a Bytes value is still a one-field boxed ADT cell with its payload at offset 16), because lib/tir/repr.ml, llvm_case.ml and drop.ml all encode assumptions about that shape, and because the C runtime builds these cells by hand.
to_list/from_list were O(1) under the old representation and are O(n) now. That is the one regression; see the design doc's audit of the callers.
This module is self-contained: it does not depend on List. or String. being loaded, so it can be tested or used in isolation.
Types
Functions
Concatenate two Bytes values. O(n).
Examples
march> Bytes.to_list(Bytes.concat(Bytes.from_list([1, 2]), Bytes.from_list([3])))
[1, 2, 3]Decode a padded standard Base64 string to Bytes. Returns Err if the length is not a multiple of 4 or a character is outside the Base64 alphabet. Decoding stops at the first padded group, so anything after it is ignored.
Examples
march> Result.map(Bytes.decode_base64("TWE="), Bytes.to_string)
Ok("Ma")
march> Bytes.decode_base64("TWF")
Err("invalid base64: length must be a multiple of 4")
march> Bytes.decode_base64("T!Fu")
Err("invalid base64 character")Decode UTF-8 bytes to a String. Always returns Ok(s) (same as to_string): the bytes are not validated, so invalid UTF-8 is not reported as an error.
Examples
march> Bytes.decode_utf8(Bytes.from_list([104, 105]))
Ok("hi")Encode Bytes as a standard Base64 string (+, / alphabet, with = padding).
Examples
march> Bytes.encode_base64(Bytes.from_string("Man"))
"TWFu"
march> Bytes.encode_base64(Bytes.from_string("M"))
"TQ=="
march> Bytes.encode_base64(Bytes.empty())
""Encode a String as UTF-8 bytes. Equivalent to from_string for March strings.
Examples
march> Bytes.to_list(Bytes.encode_utf8("ü"))
[195, 188]Parse a hex string (upper- or lowercase digits, no 0x prefix or separators) to Bytes. Returns Err("hex string must have even length") for an odd-length input and Err("invalid hex character") for any non-hex character.
Examples
march> Result.map(Bytes.from_hex("00ffAb"), Bytes.to_list)
Ok([0, 255, 171])
march> Bytes.from_hex("abc")
Err("hex string must have even length")
march> Bytes.from_hex("zz")
Err("invalid hex character")Wrap a List(Int) of byte values (0-255) as Bytes. O(n). Values outside 0-255 are not supported: the native backends keep only the low 8 bits, while the in-browser REPL raises an error.
Examples
march> Bytes.to_list(Bytes.from_list([72, 105, 0, 255]))
[72, 105, 0, 255]
march> Bytes.to_string(Bytes.from_list([72, 105]))
"Hi"Convert a String to Bytes (its raw UTF-8 byte values, each 0-255). O(1).
Examples
march> Bytes.length(Bytes.from_string("héllo"))
6Copy [a]'s bytes into a fresh immutable Bytes. O(n) copy.
Examples
march> Bytes.length(Bytes.from_u8_arr(Bytes.to_u8_arr(Bytes.from_string("abcd"))))
4Byte at position i (0-based), as an Int 0-255. Panics if i is negative or >= length(b). O(1).
Examples
march> Bytes.get(Bytes.from_string("abc"), 1)
98True if the Bytes is empty. O(1).
Examples
march> Bytes.is_empty(Bytes.empty())
true
march> Bytes.is_empty(Bytes.from_list([0]))
falseNumber of bytes (not characters). O(1).
Examples
march> Bytes.length(Bytes.from_list([1, 2, 3]))
3
march> Bytes.length(Bytes.from_string("é"))
2Sub-slice: len bytes starting at position start. Out-of-range arguments are clamped rather than panicking: a negative start is treated as 0, a slice running past the end is cut short, and a start at or past the end gives empty Bytes. O(len).
Examples
march> Bytes.to_string(Bytes.slice(Bytes.from_string("hello world"), 6, 5))
"world"
march> Bytes.to_string(Bytes.slice(Bytes.from_string("hello"), 3, 10))
"lo"
march> Bytes.is_empty(Bytes.slice(Bytes.from_string("hello"), 9, 2))
trueEncode Bytes as a lowercase hexadecimal string, two digits per byte.
Examples
march> Bytes.to_hex(Bytes.from_list([0, 15, 171, 255]))
"000fabff"
march> Bytes.to_hex(Bytes.empty())
""Unwrap Bytes to a List(Int) of byte values (each 0-255). O(n).
Examples
march> Bytes.to_list(Bytes.from_string("hé"))
[104, 195, 169]
march> Bytes.to_list(Bytes.empty())
[]Convert Bytes back to a String (bytes interpreted as UTF-8, not validated). O(1).
Examples
march> Bytes.to_string(Bytes.from_list([109, 97, 114, 99, 104]))
"march"Copy [b]'s bytes into a fresh NativeU8Arr for SIMD/numeric work. O(n) copy: the layouts cannot be shared.
Examples
march> Bytes.to_list(Bytes.from_u8_arr(Bytes.to_u8_arr(Bytes.from_list([1, 2, 3]))))
[1, 2, 3]