Reference

Standard library

What ships with Lira today — the builtins always in scope, and the 21 importable std modules. Every signature here was read from the source, every example runs.

Lira draws a clear line between two kinds of functionality. Some functions are VM builtins: implemented in the runtime and always in scope, no import required. Everything else lives in std modules, written in Lira itself under stdlib/, and pulled in with import std.X.

Builtins vs. stdlib

Builtins need no import. They cover I/O, the core array and channel operations, file handles, JSON, and a handful of numeric and time primitives:

  • I/O: println, eprintln
  • Arrays: len, push, pop
  • Channels & fibers: chan, send, recv, close, spawn, select
  • Files: file_open, file_read, file_write, file_close
  • JSON: json_parse, json_stringify, json_pretty
  • Numbers & time: abs, sqrt, time_ms, sleep
builtins.li
// No import needed — these are VM builtins, always in scope.

fn main() {
    // I/O
    println("hello from a builtin")

    // Collections: arrays are mutated in place by push/pop.
    let xs = [1, 2, 3]
    push(xs, 4)
    println(len(xs))      // 4
    println(pop(xs))      // 4

    // Numbers
    println(abs(-7))      // 7
    println(sqrt(144.0))  // 12

    // A timestamp, straight from the VM.
    let t = time_ms()
    println(t > 0)        // true
}
core.li
import std.core

fn main() {
    // abs and clamp are methods on int.
    println((-5).abs())        // 5
    println((15).clamp(0, 10)) // 10

    // min and max are free functions taking two ints.
    println(min(3, 9))         // 3
    println(max(3, 9))         // 9
}

The 21 std modules at a glance

Every module below imports cleanly today. The pure ones are self-contained Lira you can read end to end (and the examples in this page run them); the I/O ones are thin wrappers around runtime builtins — file handles, sockets, the system clock, env vars — so they are documented by signature.

Numbers & collections

core extends int with abs / clamp and adds min / max. math hangs single-argument operations off int and float and exposes multi-argument helpers as free functions. collections adds methods to typed arrays.

std.core pure
int::abs() -> int
int::clamp(min, max) -> int
min(a: int, b: int) -> int
max(a: int, b: int) -> int
std.math pure
// constants (functions returning float)
math_pi() · math_e() · math_tau() · math_sqrt2() · math_ln2() · math_log10e()
// int methods
int::{sign, factorial, fibonacci, is_prime, is_even, is_odd}() -> ...
int::{is_power_of_two, square, cube, next_power_of_two, popcount}() -> int
int::{sum_to, sum_squares_to}() -> int · int::pow(exp) · int::mod_floor(div)
// float methods
float::{sign, square, cube, cbrt, sigmoid, relu}() -> float
float::{to_radians, to_degrees, wrap_angle, normalize_angle}() -> float
float::clamp(min, max) -> float
// free functions
lerp(a, b, t) · inverse_lerp(a, b, v) · smoothstep(e0, e1, x)
hypot(x, y) · distance(x1, y1, x2, y2) · distance3d(...)
gcd(a, b) · lcm(a, b) · binomial(n, k) · nthroot(x, n) · log(x, base)
min_float(a, b) · max_float(a, b) · approx_equal(a, b, eps)
map_range(v, in_min, in_max, out_min, out_max) · leaky_relu(x, alpha)
sec(x) · csc(x) · cot(x) · asinh(x) · acosh(x) · atanh(x)
std.collections pure
// on [int]
[int]::{map_double, map_square, filter_positive, filter_even, filter_odd}() -> [int]
[int]::{sum, product, min, max, count, index_of}() -> int · [int]::average() -> float
[int]::{reverse, unique, sort, sort_desc}() -> [int]
[int]::{take(n), skip(n), slice(start, end), concat(other)} -> [int]
[int]::contains(value) -> bool · [int]::{all_truthy, any_truthy, none_truthy}() -> bool
[int]::repeat(value, n) -> [int]   (static) · [[int]]::flatten() -> [int]
// on [float] — same reductions / transforms, plus filter_negative
// on [string]
[string]::{contains(v) -> bool, index_of(v) -> int, reverse() -> [string]}
// free generators
range(start, end) -> [int] · range_step(start, end, step) -> [int]
math.li
import std.math

fn main() {
    // Methods on int.
    println((5).factorial())        // 120
    println((10).fibonacci())       // 55
    println((17).is_prime())        // true
    println((-4).sign())            // -1

    // Methods on float.
    println((180.0).to_radians())   // 3.14159...

    // Free functions for multi-argument math.
    println(gcd(48, 36))            // 12
    println(lcm(4, 6))              // 12
    println(lerp(0.0, 10.0, 0.5))   // 5

    // Constants are functions.
    println(math_pi())              // 3.141592653589793
}
collections.li
import std.collections

fn main() {
    let xs = [5, 3, 8, 1, 3, 9]

    // Reductions.
    println(xs.sum())            // 29
    println(xs.max())            // 9
    println(xs.min())            // 1

    // Transformations return new arrays.
    println(xs.sort())           // [1, 3, 3, 5, 8, 9]
    println(xs.unique().sort())  // [1, 3, 5, 8, 9]
    println(xs.filter_even())    // [8]

    // Search.
    println(xs.contains(8))      // true
    println(xs.index_of(8))      // 2
    println(xs.count(3))         // 2

    // Generators.
    println(range(0, 5))         // [0, 1, 2, 3, 4]
}

Text

strings extends the string type with methods, so you write name.to_upper() rather than to_upper(name). regex layers validators and extractors over the regex_* builtins.

std.strings pure
string::{to_upper, to_lower, capitalize, title_case}() -> string
string::{trim, trim_start, trim_end, reverse}() -> string
string::char_at(i) -> string · string::char_code(i) -> int · string::substring(start, end)
string::{index_of, last_index_of}(substr) -> int · string::contains(substr) -> bool
string::{starts_with(prefix), ends_with(suffix)} -> bool
string::split(delim) -> [string] · string::{replace, replace_first}(old, new) -> string
string::repeat(count) -> string · string::{pad_start, pad_end}(length, pad) -> string
string::{is_empty, is_blank, is_numeric, is_alpha, is_alphanumeric}() -> bool
string::count(substr) -> int · string::word_count() -> int
// free functions
from_char_code(code) -> string · join(arr: [string], delim) -> string
std.regex pure
is_email(text) · is_url(text) · is_phone(text) -> bool
is_digits(text) · is_alpha(text) · is_alphanumeric(text) -> bool
extract_numbers(text) -> [string] · extract_words(text) -> [string]
remove_whitespace(text) -> string · normalize_whitespace(text) -> string
strings.li
import std.strings

fn main() {
    let title = "  the lyre is tuned  "

    // Methods hang off the string type itself.
    println(title.trim().title_case())   // The Lyre Is Tuned

    let name = "lira"
    println(name.to_upper())             // LIRA
    println(name.capitalize())           // Lira
    println(name.repeat(3))              // liralira lira -> liraliralira
    println(name.starts_with("li"))      // true
    println(name.index_of("r"))          // 2

    // split returns [string]; join is a free function.
    let parts = "a,b,c".split(",")
    println(len(parts))                  // 3
    println(join(parts, " / "))          // a / b / c
}
regex.li
import std.regex

fn main() {
    // Validators return bool.
    println(is_email("ada@lira.dev"))     // true
    println(is_email("not-an-email"))     // false
    println(is_url("https://lira.dev"))   // true
    println(is_digits("12345"))           // true

    // Extractors return [string].
    let nums = extract_numbers("order 42 ships in 3 days")
    println(nums)                         // [42, 3]

    let words = extract_words("two words")
    println(len(words))                   // 2

    // Whitespace normalizers.
    println(normalize_whitespace("a   b    c"))  // a b c
}

Data formats

json wraps the json_parse / json_stringify / json_pretty builtins with safe helpers. url, uuid and hash sit on their own runtime builtins.

std.json pure
// VM builtins (no import)
json_parse(text) · json_stringify(value) · json_pretty(value)
// std.json helpers
json_get(obj, key) · json_has(obj, key) -> bool · is_json_value(val) -> bool
json_object() · json_array() · json_parse_safe(text)
std.url pure
struct URL
url_parse(str) -> URL · url_build(url: URL) -> string
url_origin(url) · url_path_query(url) -> string
url_is_absolute(s) · url_is_relative(s) · url_is_http(url) · url_is_https(url) · url_is_secure(url) -> bool
query_parse(q) -> [string] · query_get(q, key) -> string · query_has(q, key) -> bool · query_build(pairs)
std.uuid pure
uuid() · generate() · random() · time_ordered() -> string
is_nil(uuid) -> bool · version(uuid) -> int
std.hash pure
// builtins: md5 / sha1 / sha256 / sha512
verify_md5(input, expected) · verify_sha1(...) · verify_sha256(...) · verify_sha512(...) -> bool
md5_salted(input, salt) -> string · sha256_salted(input, salt) -> string
json.li
import std.json

fn main() {
    // json_parse / json_stringify are VM builtins; std.json adds helpers.
    let text = "{\"name\": \"lira\", \"stars\": 7}"
    let obj = json_parse(text)

    // Index into the parsed object.
    println(obj["name"])         // lira
    println(obj["stars"])        // 7

    // json_has is a std.json helper.
    println(json_has(obj, "name"))    // true
    println(json_has(obj, "missing")) // false

    // Round-trip back to a string.
    let out = json_stringify(obj)
    println(len(out) > 0)        // true
}

Time

time adds duration helpers to int (90.minutes() -> milliseconds) and a Timestamp struct with formatting, arithmetic, and comparison methods.

std.time pure
int::{seconds, minutes, hours, days, weeks}() -> int   (durations in ms)
struct Timestamp
timestamp_now() · timestamp_from_ms(ms) · timestamp_from_date(y, m, d)
timestamp_from_datetime(y, mo, d, h, mi, s) · timestamp_parse_iso(s) -> Timestamp
Timestamp::{to_iso, format(fmt), format_date, format_time, format_datetime, format_readable}() -> string
Timestamp::{add(ms), subtract(ms)} -> Timestamp · Timestamp::diff(other) -> int
Timestamp::{elapsed, elapsed_secs}() -> int
Timestamp::{is_before, is_after, is_past, is_future, is_today}() -> bool
Timestamp::{year, month, day, hour, minute, second}() -> int · Timestamp::components() -> [int]
today() · now_time() · now_iso() -> string · timezone_offset() -> int
time.li
import std.time

fn main() {
    // int gains duration helpers: 90.minutes() -> milliseconds.
    let ninety_min = (90).minutes()
    println(ninety_min)              // 5400000

    // Build a fixed timestamp so output is deterministic.
    let t = timestamp_from_datetime(2026, 6, 24, 9, 30, 0)

    // Formatting methods on the Timestamp struct.
    println(t.format_date())         // 2026-06-24
    println(t.to_iso())              // 2026-06-24T09:30:00+00:00

    // Arithmetic returns new timestamps; diff is in milliseconds.
    let later = t.add(ninety_min)
    println(later.format_time())     // 11:00:00
    println(later.diff(t))           // 5400000

    // Component accessors.
    println(t.year())                // 2026
    println(t.hour())                // 9
}

Concurrency

sync gives you mutual exclusion and coordination built on the same channels and fibers covered in the concurrency guide. Because Lira has no RAII, locking is explicit: lock() takes the value out, unlock(v) puts one back; the with closure brackets that pair for you.

std.sync pure
struct IntMutex · new_int_mutex(initial) -> IntMutex
IntMutex::lock() -> int · IntMutex::unlock(value) · IntMutex::with(f: fn(int) -> int)
struct StringMutex · new_string_mutex(initial) -> StringMutex   (lock / unlock / with)
struct WaitGroup · new_wait_group() -> WaitGroup · WaitGroup::done() · WaitGroup::wait(n)
struct Semaphore · new_semaphore(n) -> Semaphore · Semaphore::acquire() · Semaphore::release()
sync.li
import std.sync

// Each worker increments the shared counter once, then signals done.
fn worker(m: IntMutex, wg: WaitGroup) {
    let v = m.lock()
    m.unlock(v + 1)
    wg.done()
}

fn main() {
    let counter = new_int_mutex(0)
    let wg = new_wait_group()

    let n = 5
    var i = 0
    while i < n {
        spawn worker(counter, wg)
        i = i + 1
    }

    // Block until all n workers have finished.
    wg.wait(n)

    // No RAII in Lira, so locking is explicit: lock to read, unlock to release.
    let total = counter.lock()
    counter.unlock(total)
    println(total)   // 5
}

System & I/O

Thin wrappers around runtime builtins: file handles, the system clock, the filesystem, and environment variables. Output depends on the host, so these are listed by signature.

std.io I/O
print_str(s) · print_line(s) · print_fmt(template, values: [string])
debug(label, value: int) · assert(condition: bool, message)
now_ms() -> int · delay(ms) · measure_time() -> int
std.fs I/O
read_file(path) -> string · write_file(path, content) -> bool
append_file(path, content) -> bool · exists(path) -> bool · size(path) -> int
std.os I/O
walk(path) -> [string] · exists(path) -> bool
home_dir() -> string · temp_dir() -> string
std.env I/O
get_or(name, default) -> string · get_bool(name) -> bool
is_ci() · is_debug() -> bool · user() · shell() -> string
std.path I/O
dirname(p) · basename(p) · extension(p) · stem(p) · parent(p) · normalize(p) -> string
is_absolute(p) · is_relative(p) · has_extension(p) · starts_with(p, pre) · ends_with(p, suf) -> bool
with_extension(p, ext) -> string · components(p) -> [string]
join(p1, p2) -> string · path_join(parts: [string]) -> string

Network

http and net wrap the runtime's HTTP and TCP builtins. They perform real I/O, so they are listed by signature.

std.http I/O
get(url) · get_ok(url) -> string
post_json(url, data) · post_form(url, data) · post_text(url, data) -> string
is_success(status) · is_redirect(status) · is_client_error(status) · is_server_error(status) · is_error(status) -> bool
std.net I/O
tcp_try_connect(host, port) -> int · is_connected(socket_id) -> bool
tcp_write_line(socket_id, line) -> int · tcp_write_lines(socket_id, lines: [string]) -> int
tcp_read_exact(socket_id, n) -> string · tcp_close_safe(socket_id) -> bool

Testing, logging & random

test is a stateless assertion toolkit, log a leveled logger, and random a generator over the random() builtin. random is pure-ish but nondeterministic, so it is listed by signature.

std.test I/O
describe(name) · section(name) · test(name, passed) -> bool · run_test(name, passed) -> int
summary(total, passed, failed) · skip(name, reason) · expect_fail(name, passed) -> bool
assert(c) · assert_eq(a, b) · assert_eq_str(a, b) · assert_eq_float(a, b, eps) · assert_ne(a, b)
assert_true(v) · assert_false(v) · assert_gt / gte / lt / lte(a, b)  (+ _float variants)
assert_between(v, min, max) · assert_contains(hay, needle) · assert_starts_with / ends_with
assert_len(arr, n) · assert_empty(arr) · assert_array_contains(arr, v) · assert_null / not_null(v)
count_passed(r) -> int · count_failed(r) -> int · all_passed(failed) -> bool
std.log I/O
debug(msg) · info(msg) · warn(msg) · error(msg) · fatal(msg)
log(level, msg) · level_name(level) -> string · parse_level(name) -> int
log_kv(level, msg, k, v) · log_kv2(...) · log_kv3(...) · {debug,info,warn,error}_kv(msg, k, v)
log_timing(operation, start_ms) · log_timing_level(level, operation, start_ms)
std.random I/O
random_bool() -> bool · random_range(min: float, max: float) -> float
random_index(length) -> int · shuffle_int_array(arr) -> [int]
random_digits(n) -> int · coin_flip() -> string · dice_roll() -> int · dice_roll_n(sides) -> int

Where to go next

Ready to put these to work? The guide walks through your first program, types covers generics and optionals, patterns covers matching and exhaustiveness, and concurrency goes deep on fibers, channels, and select. The examples gallery shows whole programs that run.