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; emptypytype,traceback, no cause, andoriginset to the call site (section 5.7).error.wrap(cause error, msg str) error— a new error with the message and the given cause;originis 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
}
}
}
Error fields are specified in section 5.7.
math
math.abs(int) intor(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 ofnuminbase.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.e—floatconstants.
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
}
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[]byteon 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 tonbytes from the handle’s current position; negativenis 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?— writebto 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
}
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
}
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— deadlinednanoseconds from now (the one time currency, section 15.8;30 * time.second), clamped so a child deadline never exceeds its parent’s; negativedis 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?—nonewhile 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
}
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?—nonewhen the two values are structurally equal (the comparison of section 11.2’scontains); 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?—nonewhen given an error; an error when givennone. 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
}
}
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, invokingfper 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
}
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
Responsewithstatus404 and anoneerror. Only transport-level failures (connection refused, timeout, invalid request) are error values, with the zeroResponsein the value slot. - Redirects are followed automatically.
- For
http.request, an empty body on a GET request sends no body. http.streamPOSTsbodyand invokesfonce per response line as lines arrive, before the response completes (server-sent events are consumed this way). The returnedResponse.bodyholds 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 markedpreemptible) 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:trueand hold the card if it was free,falseif 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--lowsemantics).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_FILEif set, else/etc/gputex/env;KEY=VALUElines) 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_DIRrelocates the state directory (tests, sandboxes) andGPUTEX_ENV_FILErelocates the managed environment file. - On non-unix builds (the playground) every
gpufunction 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
}
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):
| Constant | Value |
|---|---|
time.nanosecond | 1 |
time.microsecond | 1000 |
time.millisecond | 1000000 |
time.second | 1000000000 |
time.minute | 60 · 10⁹ |
time.hour | 3600 · 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 fordnanoseconds, thennone. A non-positivedreturnsnonewithout 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)
}
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, ornonewhen 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)
}
os.args() []str— the program’s arguments: everything after the source file on the command line (nv prog.nv a bandnevla run prog.nv a bboth 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 (eofon end of input). A prompt is the caller’s ownprintf; when a program runs through the CLI runner its output is streamed unbuffered, so a prompt written beforeos.readlineis 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 ins.find(s str) Match?— the leftmost match, ornone.find_all(s str) []Match— every non-overlapping match, left to right; an empty list when there are none.replace(s str, repl str) str—swith every match replaced.$1,$2, and$nameinreplsubstitute 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
}
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 (typicallyos.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 onp.valueswork too and narrow as options (7.6).
Grammar, Go-shaped:
--name value,--name=value,-s value,-s=value.- A toggle takes no value;
--name=valueon a toggle is an error. - Parsing stops at
--(consumed) or at the first argument that does not begin with-; everything from there lands inrestverbatim. A bare-is an argument, not a flag. -hand--helpare synthesized: parse returns an error whose message is the usage text, one line per flag, value flags showing their fallback. Declaring a flag namedhelpor with shorthis 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)andfloat(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
}
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 forproc.execwith an emptyCmdaroundargv.proc.exec(c Ctx, cmd Cmd) (Result, error?)— the full form.cmd.dirsets the working directory (""inherits);cmd.enventries 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-emptycmd.stdinis 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 withrun’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-emptycmd.logappends the stream to that file instead;readlineon 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, theos.readlinecontract), 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, waitgracenanoseconds, kill. Idempotent; stopping an exited child isnone.
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
}
In contexts with no processes (the browser playground) every proc
function reports its absence as a fault naming the build.