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
  • 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

Every module below imports cleanly today. The pure ones are self-contained Lira you can read end to end; the I/O ones are thin, honest wrappers around runtime builtins (file handles, sockets, the system clock, env vars).

std.collections pure

Array methods — sum, min, max, sort, unique, filter_even, contains, slice, concat — plus range / range_step generators.

std.core pure

abs and clamp as methods on int; min and max as free functions.

std.math pure

factorial, fibonacci, is_prime, sign on numbers; gcd, lcm, lerp, hypot and the math_pi / math_e constants.

std.strings pure

Methods on the string type: to_upper, trim, split, replace, starts_with, pad_start, and more; join as a free function.

std.json pure

Helpers (json_has, json_get, json_object) over the json_parse / json_stringify builtins.

std.sync pure

IntMutex, StringMutex, WaitGroup, Semaphore — explicit lock/unlock and with, built honestly on channels.

std.regex pure

is_email, is_url, extract_numbers, extract_words over the regex_* builtins.

std.random pure

random_bool, random_range, dice_roll, coin_flip, shuffle_int_array over the random() builtin.

std.uuid pure

uuid, time_ordered, is_nil, version — wrappers over uuid_v4 / uuid_v7 builtins.

std.url pure

url_parse / url_build into a URL struct, plus query_parse / query_get / query_has.

std.path pure

dirname, basename, extension, starts_with — Unix-style path string manipulation.

std.hash pure

verify_md5 / verify_sha256, salted variants over the md5 / sha1 / sha256 / sha512 builtins.

std.time pure

A Timestamp struct with timestamp_now, parsing, and formatting helpers.

std.env I/O

get_or, get_bool, is_ci, user, shell over the env_get / env_args builtins.

std.fs I/O

read_file, write_file, append_file, exists, size — high-level file ops.

std.os I/O

walk, exists, home_dir, temp_dir.

std.io I/O

print_str, print_line, print_fmt, assert, plus now_ms / measure_time timing helpers.

std.log I/O

Leveled logging — debug, info, warn, error, fatal — as stateless functions.

std.http I/O

get, get_ok, post_json plus status-class predicates (is_success, is_redirect, …).

std.net I/O

TCP helpers — tcp_try_connect, tcp_write_line, tcp_read_exact, tcp_close_safe.

std.test I/O

describe, test, assert, assert_eq, summary — a tiny stateless assertion toolkit.

Strings

std.strings extends the string type with methods, so you write name.to_upper() rather than to_upper(name). split returns [string]; the inverse, join, is a free function.

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
}

Math

std.math hangs methods off int and float for the single-argument operations, and exposes free functions for the multi-argument ones. Note that many trig and rounding functions (sin, cos, floor, pow) are VM builtins — std.math adds the rest.

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

std.collections adds methods to typed arrays — [int], [float], and [string]. Transformations like sort and filter_even return fresh arrays; reductions like sum and max return a scalar.

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]
}

JSON

The heavy lifting — json_parse and json_stringify — is built into the VM. std.json layers on convenience helpers like json_has and json_get. Parse a string, index into it, and round-trip it back:

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
}

Sync

std.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 form brackets that pair for you.

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
}

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.