Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Standard library

Generated from the language spec, which is normative.

Standard library modules are imported by bare name: import "math". The module name then acts as a namespace: math.sqrt(2.0), math.pi. The v1 modules are math, error, file, ctx, and http.

Where a stdlib signature below ends in error? or (T, error?), failures are ordinary error values subject to chapter 10; stdlib functions do not fault on I/O failure.

error

The constructors error.new and error.wrap require no import; they are part of the core language. import "error" remains legal and adds nothing.

  • error.new(msg str) error — a new error with the message; empty pytype, traceback, no cause, and origin set to the call site (section 5.7).
  • error.wrap(cause error, msg str) error — a new error with the message and the given cause; origin is the wrap site.
fn fetch() (error?) {
    return error.new("connection refused")
}

fn main() {
    err := fetch()
    if err != none {
        wrapped := error.wrap(err, "startup failed")
        print(wrapped.msg)          // startup failed
        cause := wrapped.cause
        if cause != none {
            print(cause.msg)        // connection refused
        }
    }
}

▸ run it in the playground

Error fields are specified in section 5.7.

math

  • math.abs(int) int or (float) float — absolute value, polymorphic over the two numeric types; abs(-9223372036854775808) faults with integer overflow.
  • math.min(int, int) int / (float, float) float, math.max(...) likewise — both arguments the same numeric type.
  • math.sqrt(float) float — square root.
  • math.cos(float) float, math.sin(float) float, math.tan(float) float — trigonometry, radians.
  • math.pow(base float, pow float) float — exponentiation.
  • math.exp(pow float) float — e raised to the argument.
  • math.ln(num float) float — natural logarithm.
  • math.log(base float, num float) float — logarithm of num in base.
  • math.floor(float) int — round down. math.ceil(float) int — round up. math.round(float) int — round half away from zero (not banker’s rounding).
  • math.pi, math.efloat constants.
import "math"

fn main() {
    print(math.max(2, 40) + math.abs(-2))     // 42
    print(math.pow(2.0, 10.0))                // 1024
    printf("%.4f\n", math.log(2.0, 1024.0))   // 10.0000
    print(math.round(2.5))                    // 3
    printf("%.5f\n", math.pi)                 // 3.14159
}

▸ run it in the playground

file

Paths are str. File contents are UTF-8 str or raw bytes []byte.

  • file.read(path str) (str, error?) — whole-file read; the value slot is "" on error.
  • file.write(path str, s str) error? — create or truncate, then write.
  • file.append(path str, s str) error? — create if missing, append.
  • file.readbytes(path str) ([]byte, error?) — whole-file read; the value slot is an empty []byte on error.
  • file.writebytes(path str, b []byte) error? — create or truncate, then write binary data.
  • file.exists(path str) bool — existence test; never errors.
  • file.list(dir str) ([]str, error?) — entry names, sorted lexicographically.
  • file.remove(path str) error? — remove a file or an empty directory.
  • file.mkdir(path str) error? — create the directory and any missing parents.
  • file.glob(pattern str) ([]str, error?) — the paths matching a shell-style pattern, sorted. * and ? stay within one path segment and do not match a leading dot; ** crosses directories; [...] matches a character class. Relative patterns resolve against the process working directory. Zero matches is an empty list; only a malformed pattern is an error. Unreadable directories along the way are skipped, as in the shell.
  • file.modified(path str) (int, error?) — the file’s last modification time, epoch nanoseconds (the one time currency, section 15.8).

Importing "file" also declares the opaque struct type File, a handle to an open file, with reference semantics (section 11.1); file.open and file.create make them. There is no seek and no append-mode handle in v1.

  • file.open(path str) (File, error?) — open an existing file for reading.
  • file.create(path str) (File, error?) — create or truncate, open for writing.
  • f.read(n int) ([]byte, error?) — read up to n bytes from the handle’s current position; negative n is treated as 0 (an empty read, no error). EOF is ([]byte{}, none), not an error value. Reading a closed handle is an error value, not a fault.
  • f.write(b []byte) error? — write b to the handle. Writing a closed handle is an error value, not a fault.
  • f.close() error? — close the handle. Idempotent: closing an already-closed handle succeeds.
import "file"

fn main() (error?) {
    path := "/tmp/nevla-book-handle-example.bin"
    w := check file.create(path)
    check w.write([]byte{1, 2, 3})
    check w.close()

    r := check file.open(path)
    chunk := check r.read(2)
    print(chunk)                     // [1, 2]
    check r.close()
    check file.remove(path)
    return none
}

▸ run it in the playground

import "file"

fn main() (error?) {
    path := "/tmp/nevla-book-example.txt"
    check file.write(path, "one\n")
    check file.append(path, "two\n")
    body := check file.read(path)
    print(body.lines())              // [one, two]
    print(file.exists(path))         // true
    check file.remove(path)
    return none
}

▸ run it in the playground

ctx

Importing "ctx" also brings the opaque struct type Ctx into scope. A Ctx is a cancellation handle: a deadline plus an interrupt flag. Ctx values are handles with reference semantics (section 11.1) and cannot be constructed with a struct literal (section 7.2.3).

  • ctx.background() Ctx — never done.
  • ctx.timeout(parent Ctx, d int) Ctx — deadline d nanoseconds from now (the one time currency, section 15.8; 30 * time.second), clamped so a child deadline never exceeds its parent’s; negative d is treated as 0.
  • ctx.interrupt(parent Ctx) Ctx — additionally becomes done when the process receives SIGINT.

Methods on Ctx:

  • done() bool — whether the deadline has passed or the interrupt fired.
  • err() error?none while live; "deadline exceeded" or "interrupted" when done.
import "ctx"

fn main() {
    c := ctx.timeout(ctx.background(), 0)     // already expired
    print(c.done())                           // true
    e := c.err()
    if e != none {
        print(e.msg)                          // deadline exceeded
    }
    print(ctx.background().done())            // false
}

▸ run it in the playground

test

Importing "test" provides the helpers nevla test is built around (section 17.7); each returns error? so it composes with check, and each failure carries an origin (section 5.7).

  • test.eq(got, want) error?none when the two values are structurally equal (the comparison of section 11.2’s contains); otherwise an error naming both sides. Comparing values deeper than the implementation limit faults.
  • test.neq(got, unwanted) error? — the negation.
  • test.err(e error?) error?none when given an error; an error when given none. Asserts that something failed.
  • test.skip(reason str) error? — an error the test runner reports as skipped rather than failed.
import "test"

fn main() {
    print(test.eq([1, 2], [1, 2]) == none)   // true: structural
    bad := test.eq(2, 3)
    if bad != none {
        print(bad.msg)                        // expected 3, got 2
    }
}

▸ run it in the playground

http

Importing "http" also declares two struct types:

struct Request  { method str, url str, body str, headers map[str]str }
struct Response { status int, body str, headers map[str]str }
  • http.get(c Ctx, url str) (Response, error?) — GET.
  • http.post(c Ctx, url str, body str) (Response, error?) — POST with the given body.
  • http.request(c Ctx, req Request) (Response, error?) — any method, with headers.
  • http.stream(c Ctx, url str, body str, f fn(str)) (Response, error?) — POST, invoking f per response line as it arrives.
import "ctx"
import "http"
import "time"

fn main() (error?) {
    c := ctx.timeout(ctx.background(), 5 * time.second)
    resp, err := http.get(c, "http://localhost:9/unreachable")
    if err != none {
        print("transport error, as expected here")
    } else {
        print(resp.status)
    }
    return none
}

▸ run it in the playground

Behavior:

  • If the ctx is already done, the call returns an error before any network I/O.
  • A live ctx deadline bounds the whole request; without a deadline, an implementation-defined default timeout applies (30 seconds in the reference implementation).
  • A completed HTTP exchange is a success regardless of status code: a 404 is a Response with status 404 and a none error. Only transport-level failures (connection refused, timeout, invalid request) are error values, with the zero Response in the value slot.
  • Redirects are followed automatically.
  • For http.request, an empty body on a GET request sends no body.
  • http.stream POSTs body and invokes f once per response line as lines arrive, before the response completes (server-sent events are consumed this way). The returned Response.body holds the accumulated lines, newline terminated, so the program can reparse the full payload afterward; closures historically could not accumulate it themselves (pre-ADR-0010 closures captured by value); kept for compatibility. Its default deadline, absent a ctx deadline, is 300 seconds rather than 30.
  • Response header names are as received; values that are not valid strings read as "".

gpu

GPU sharing. The module speaks the gputex lock protocol (an advisory flock plus a holder registry under $GPUTEX_DIR, default ~/.gputex; the contract is documented in the gputex repository), so a nevla program coordinates with every other job on the host — wrapped in the gputex CLI or not — without an external wrapper.

Every function takes the card id first ("default" on single-card hosts; multi-card hosts name their cards, e.g. "cuda0" — the host’s convention is whatever gputex status lists); label names the job for status displays. A card id that is empty or contains a path separator is an error value (“bad card id”): ids become file names in the shared state directory.

  • gpu.lock(card str, label str) error? — take the card exclusively. Preemptible holders (shared acquirers, and any registry entry marked preemptible) on this host are first asked to leave with SIGTERM, given about ten seconds, then removed with SIGKILL; holders on other hosts are never signaled, dead registry entries are pruned, and a non-preemptible holder is simply waited out, blocking in the kernel until the card is free. The evict-then-take sequence retries a bounded number of times (flock has no fairness; a new shared holder can slip in) before settling into the blocking wait. Errors if this program already holds that card.
  • gpu.trylock(card str, label str) (bool, error?) — non-blocking probe: true and hold the card if it was free, false if it is busy (including when held by this program). Busy is data, not an error; the error slot is for real failures (an unwritable state directory).
  • gpu.shared(card str, label str) error? — take the card as a shared, lowest-priority holder: many coexist, all yield to an exclusive acquirer, which may terminate them (the gputex --low semantics).
  • gpu.unlock(card str) error? — release. Errors if that card is not held.

Behavior:

  • A program may hold several cards at once (training on one while embedding on another), one hold per card; a second acquire of a held card is an error.
  • A hold lasts until gpu.unlock(card) or process exit — any exit. The kernel releases the flock when the process dies, so a fault, kill, or crash never strands a card.
  • Acquiring also injects the managed environment ($GPUTEX_ENV_FILE if set, else /etc/gputex/env; KEY=VALUE lines) into the process environment, existing values winning: taking the card and getting the metrics contract (MLFLOW_TRACKING_URI) are one step, as with the CLI.
  • Two environment variables configure the module, mirroring gputex: GPUTEX_DIR relocates the state directory (tests, sandboxes) and GPUTEX_ENV_FILE relocates the managed environment file.
  • On non-unix builds (the playground) every gpu function faults (“gpu.lock is not available in this build”).
import "gpu"

fn main() (error?) {
    check gpu.lock("default", "tinyllama eval")
    // the card is ours until unlock or exit
    check gpu.unlock("default")

    ok, err := gpu.trylock("default", "opportunistic sweep")
    if err != none {
        return err
    }
    if !ok {
        print("card busy; skipping")
        return none
    }
    check gpu.unlock("default")
    return none
}

▸ run it in the playground

time

Clocks, sleeping, and civil time. The single time currency is int nanoseconds: every duration and every instant in the standard library is an integer count of nanoseconds (exact in int until the year 2262), and durations are written with the constants below.

Importing "time" also declares:

struct Parts { year int, month int, day int, hour int, minute int, second int }

Constants (all int):

ConstantValue
time.nanosecond1
time.microsecond1000
time.millisecond1000000
time.second1000000000
time.minute60 · 10⁹
time.hour3600 · 10⁹

Functions:

  • time.now() int — the wall clock, nanoseconds since the Unix epoch.
  • time.clock() int — a monotonic clock, nanoseconds since an arbitrary origin; only differences are meaningful. Unaffected by wall-clock adjustment.
  • time.sleep(c Ctx, d int) error? — block for d nanoseconds, then none. A non-positive d returns none without blocking. If the ctx is done, or becomes done while sleeping, the sleep ends promptly and returns the ctx error ("deadline exceeded" or "interrupted"). Wake-up latency after the ctx ends is implementation-defined but bounded (the reference implementation checks at least every 50ms).
  • time.parts(epoch int) Parts — the local civil time for an epoch instant, split into fields. Nanoseconds within the second truncate. An epoch outside the representable civil range faults.
import "ctx"
import "time"

fn main() {
    t0 := time.clock()
    e := time.sleep(ctx.background(), 250 * time.millisecond)
    if e != none {
        print("interrupted early: " + e.msg)
    }
    elapsed_ms := (time.clock() - t0) / time.millisecond
    p := time.parts(time.now())
    printf("%d:%d:%d slept about %dms\n", p.hour, p.minute, p.second, elapsed_ms)
}

▸ run it in the playground

In contexts with no usable clock (the browser playground) time.now, time.clock, time.sleep, and time.parts report their absence as a fault naming the build, the same contract as ctx.timeout (15.4).

os

The process’s own surroundings. Absence is an option or an error value, never a sentinel.

  • os.workdir() (str, error?) — the current working directory as an absolute path.
  • os.env(name str) str? — the environment variable’s value, which may be the empty string, or none when unset (or set to bytes that are not valid unicode). There is no get-with-default; narrowing is the mechanism:
import "os"

fn main() {
    bin := "./a.out"
    v := os.env("BIN")
    if v != none {
        bin = v
    }
    print(bin)
}

▸ run it in the playground

  • os.args() []str — the program’s arguments: everything after the source file on the command line (nv prog.nv a b and nevla run prog.nv a b both yield ["a", "b"]). In contexts with no command line (tests, embedding) the list is empty.
  • os.readline() (str, error?) — read one line from standard input. The returned string excludes the line terminator. End of input and read failures are error values, not faults (eof on end of input). A prompt is the caller’s own printf; when a program runs through the CLI runner its output is streamed unbuffered, so a prompt written before os.readline is visible before the read blocks.

In contexts with no operating system to speak of (the browser playground) every os function reports its absence as a fault naming the build.

regex

Pattern matching. Importing "regex" also declares:

struct Match { text str, start int, end int, groups []str }

and the opaque struct type Re, a compiled pattern. Re values are handles with reference semantics (section 11.1) and cannot be constructed with a struct literal; regex.compile makes them.

  • regex.compile(pattern str) (Re, error?) — compile a pattern. A malformed pattern is an error naming the problem.

Methods on Re:

  • matches(s str) bool — whether the pattern matches anywhere in s.
  • find(s str) Match? — the leftmost match, or none.
  • find_all(s str) []Match — every non-overlapping match, left to right; an empty list when there are none.
  • replace(s str, repl str) strs with every match replaced. $1, $2, and $name in repl substitute capture groups; $$ is a literal dollar.

A Match is plain data: text is the matched text, start and end are character indices into the subject (half-open, so s[m.start:m.end] == m.text, section 7.6), and groups holds captures 1 through n in order, with a group that did not participate reading as "".

The flavor is the RE2 family (the Rust regex crate): matching runs in time linear in the input, and backreferences and lookaround do not exist; a pattern that wants them is a compile error naming the missing feature. Case-insensitivity and other flags are written inline ((?i), (?m), (?s)). Full backtracking semantics remain available through the bridge (import py "re").

import "regex"

fn main() (error?) {
    re := check regex.compile("(?i)(\\w+)=(\\d+)")
    for _, m := range re.find_all("A=1 b=22 c=x") {
        printf("%s is %s\n", m.groups[0], m.groups[1])
    }
    return none
}

▸ run it in the playground

flag

Command-line flags, data-shaped: no registry, no global state, no output. Importing "flag" also declares:

struct Flag { name str, short str, fallback str, usage str, toggle bool }
struct Parsed { values map[str]str, rest []str }
  • flag.value(name str, short str, fallback str, usage str) Flag — a flag that takes a value.
  • flag.toggle(name str, short str, usage str) Flag — a presence flag; parses to "true", fallback "false".
  • flag.parse(argv []str, flags []Flag) (Parsed, error?) — parse argv (typically os.args()). Pure: same inputs, same outputs.
  • flag.get(p Parsed, name str) str — read a parsed value. Parse fills every declared flag with its fallback first, so a lookup by declared name always answers; an undeclared name reads "". Plain map reads on p.values work too and narrow as options (7.6).

Grammar, Go-shaped:

  • --name value, --name=value, -s value, -s=value.
  • A toggle takes no value; --name=value on a toggle is an error.
  • Parsing stops at -- (consumed) or at the first argument that does not begin with -; everything from there lands in rest verbatim. A bare - is an argument, not a flag.
  • -h and --help are synthesized: parse returns an error whose message is the usage text, one line per flag, value flags showing their fallback. Declaring a flag named help or with short h is itself an error. The module never writes output and never exits; main decides what an error means (usually print it and return it).
  • An unknown flag, a toggle given a value, and a value flag at the end of argv with nothing to take are errors carrying the usage text.
  • Values are strings; int(x) and float(x) conversions are the typed layer, already mandatory-checked.
import "flag"
import "os"

fn main() (error?) {
    p := check flag.parse(os.args(), [
        flag.value("addr", "a", ":8080", "listen address"),
        flag.toggle("verbose", "v", "log more"),
    ])
    if flag.get(p, "verbose") == "true" {
        print("listening on " + flag.get(p, "addr"))
    }
    return none
}

▸ run it in the playground

proc

Subprocesses. Importing "proc" also declares:

struct Cmd { argv []str, dir str, env map[str]str, stdin str, log str }
struct Result { status int, stdout str, stderr str }

and the opaque struct type Proc, a handle to a started child, with reference semantics (section 11.1); proc.start makes them.

  • proc.run(c Ctx, argv []str) (Result, error?) — run to completion and capture output. Shorthand for proc.exec with an empty Cmd around argv.
  • proc.exec(c Ctx, cmd Cmd) (Result, error?) — the full form. cmd.dir sets the working directory ("" inherits); cmd.env entries are ADDED to the inherited environment, overriding on collision (an empty map inherits unchanged; there is no way to drop the inherited environment in v1); a non-empty cmd.stdin is written to the child and closed.
  • proc.attach(c Ctx, argv []str) (int, error?) — run a child that OWNS the terminal: stdin, stdout, and stderr are inherited, nothing is captured, and the call blocks until the child exits, returning its status with run’s exit semantics. For editors, REPLs, and anything else interactive.
  • proc.start(cmd Cmd) (Proc, error?) — start a long-running child. Its stderr merges into stdout as one stream, interleaved at line granularity in arrival order. A non-empty cmd.log appends the stream to that file instead; readline on a logged child returns an error naming the file.

Exit semantics for run/exec: a child that ran and exited zero is a Result with a none error. A nonzero exit fills Result AND sets the error (exit status 3): handling stays mandatory, the output stays data. A child terminated by a signal reports status -1 and the error names the signal. Failure to spawn at all (missing binary, bad directory) returns the zero Result and an error. If the ctx is already done, nothing spawns. If the ctx ends while the child runs, the child is terminated (then killed after a short grace) and the call returns the ctx error with output captured so far and status -1.

Methods on Proc:

  • pid() int — the child’s process id.
  • running() bool — whether the child is still alive.
  • readline(c Ctx) (str, error?) — the next line of the merged stream, blocking until one arrives, the stream ends (eof, the os.readline contract), or the ctx ends (the ctx error; the child is left alone).
  • wait(c Ctx) (int, error?) — block until the child exits and return its status, or return the ctx error when the ctx ends first (the handle stays valid; waiting again is fine).
  • stop(grace int) error? — terminate politely, wait grace nanoseconds, kill. Idempotent; stopping an exited child is none.

The runtime owns the pipes: child output is moved into buffers (or the log file) by the implementation the moment it exists, so a child that fills one pipe while the program reads another cannot deadlock. No part of a program observes the threads this implies.

import "ctx"
import "proc"
import "time"

fn main() (error?) {
    c := ctx.timeout(ctx.background(), 30 * time.second)
    r := check proc.run(c, ["git", "status", "--short"])
    printf("%s", r.stdout)

    p := check proc.start(Cmd{
        argv: ["make", "serve"],
        dir: "", env: map[str]str{}, stdin: "", log: "/tmp/serve.log",
    })
    printf("serving as pid %d\n", p.pid())
    check p.stop(2 * time.second)
    return none
}

▸ run it in the playground

In contexts with no processes (the browser playground) every proc function reports its absence as a fault naming the build.