Interop & Stdlib

A new language is only as useful as what it can reach. Lumina reaches C directly, ships a practical standard library, and treats calls to an LLM as a typed, compiler-checked effect.

Calling C: @ExternC

@Trusted
@ExternC function strlen(s: String): Integer;

@Trusted
@ExternC function atoi(s: String): Integer;

function main(console: Console): Integer {
    console.println(`strlen = {strlen("lumina")}, atoi = {atoi("42")}`)
    return 0
}
strlen = 6, atoi = 42

A short stdlib tour

import { sha256Hex } from "library/std/crypto"
import { regex_matches } from "library/std/regex"

function main(console: Console): Integer {
    body = "{\"user\": {\"name\": \"Ada\", \"langs\": [\"ts\", \"rust\"]}}"
    name = Json.getPath(body, "user.name") ?? "unknown"
    second = Json.getPath(body, "user.langs.1") ?? "none"
    console.println(`{name} likes {second}`)

    console.println(sha256Hex("abc"))

    isDate = regex_matches("^[0-9]+-[0-9]+-[0-9]+$", "2026-10-25")
    console.println(`date? {isDate == 1}`)
    return 0
}
Ada likes rust
ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
date? true

Json.getPath returns String?, so a missing path is handled with ?? instead of an exception. regex_matches returns 1 or 0. Capture groups, counted quantifiers and replace round out the regex module in progress.

What else is there:

AreaWhat you get
Stringsviews (StringView), splitting, search, case, templates, a string builder
Bytes and I/OBytes / MutableBytes, BytesView, buffer pools, Decoder / Encoder, line streams over files
Data formatsJSON, CSV, INI, TOML, XML
CryptoSHA-256, MD5, HMAC, PBKDF2, Base64, ed25519, random tokens
Randomnessa Random capability with SystemRandom, SeededRandom and FixedRandom (for tests)
CollectionsArray, Map, Set, plus List, Deque and PriorityQueue in library/collections
NetworkingTCP sockets with peek / spliceTo (see Zero-Copy & Servers), WebSocket and JSON-RPC modules in stdlib/net
Text algorithmsLevenshtein, Jaro-Winkler, Soundex, Metaphone, Porter stemmer, slugify
MiscUUIDs, URL encoding, time, ANSI colors, property tests

Language_Stdlib.md is the reference and FEATURE_STATUS.md tracks per-module status. The standard library lives in two places: library/std/ (core modules) and stdlib/ (larger subsystems such as ai, net and mcp). This split is visible in the import paths.

LLMs as a typed effect

Most languages treat an LLM call as just another HTTP request, so nothing stops a helper three layers down from quietly calling a model. In Lumina, calling a model is an algebraic effect, Llm, declared in stdlib/ai/effects.lumina. There is no std.ai module: the code lives in stdlib/ai/, stdlib/agent/ and stdlib/mcp/ and is imported by path, for example import { askLlm, Llm } from "stdlib/ai/effects".

public effect Llm {
    complete(req: LlmRequest): Response!LlmError
}

Any function that transitively performs Llm.complete carries / {Llm} in its effect row. If that effect reaches main without a handle ... with Llm { ... }, the program does not compile (E9003). Code that goes through Llm shows it in its types, and the compiler enforces that. Direct functions such as callLlm, the agent loop and the builtin http_* calls do not carry the effect in progress.

A test handler resumes with a canned answer, so no network and no API key are needed:

result: Response!LlmError = handle { askLlm(provider, "Tell me a short story about a compiler.") } with Llm {
    complete(req) => resume(Ok(Response {
        content: "Once upon a time, a compiler caught a missing effect handler and saved the kingdom.",
        tokensUsed: 18, inputTokens: 9, outputTokens: 18, stopReason: "stop",
        toolCalls: [], latencyMs: 0, model: "stub-claude-sonnet", provider: "stub"
    }))
}
$ lumina run examples/ai_effects/main.lumina
Story: Once upon a time, a compiler caught a missing effect handler and saved the kingdom.
  (model: stub-claude-sonnet, tokens: 18)
Err: RateLimit — back off and retry.

Failures are a closed enum (RateLimit, Timeout, AuthFailed, BudgetExceeded, BadResponse, NetworkError, MissingApiKey), so match forces you to decide what each one means. The test handler and the E9003/E9004 checks are implemented. No production Llm handler ships in stdlib designed: you write the bridge from Llm.complete to callWithRetry yourself. Around the effect, stdlib/ai/ has provider configs, retry policies, rate limiting, budgets, provider routing and conversations.

Tool calling is in progress: wire formats for OpenAI and Anthropic and an agent loop with injected closures exist. Approval is not wired into the loop designed, and streaming is designed. The local llama backend needs a native shim library and has no capability gating in progress.

The full status table is in Language_AI.md.

Reflection

Types can be inspected at compile time and at run time through .reflect:

struct Point {
    public x: Integer
    public y: Integer
}

function main(console: Console): Integer {
    p = Point { x: 1, y: 2 }
    console.println(`{Point.reflect.name} is a {p.reflect.kind} with {p.reflect.fieldCount} fields`)
    return 0
}
Point is a struct with 2 fields

Reflection drives derivation: listing an interface in implements triggers a @CompileTime static derive() that generates the implementation from the type's shape in progress. Language_Advanced_Reflection.md lists what is in place.

Next: Tooling