The standard library

Modules written in Nexium and embedded in the compiler. import std.<module> makes it available as <module>.function(...); nothing to install or link. The core containers (List, String, Map), formatting, and the math, io, os, time, random, mem, and process namespaces are compiler builtins and are documented in language.md.

Sources are in std/; each module carries its own test blocks, run by nx test std/<module>.nx and by the test harness. This file is generated by scripts/std_docs.py from the doc comments.

modulewhat
std.argscommand-line argument parsing, written in Nexium.
std.bytesencodings and byte-level utilities, written in Nexium.
std.fsfiles, directories and paths, written in Nexium.
std.httpan HTTP/1.1 client and a small server, written in Nexium over
std.jsona JSON parser and serializer, written in Nexium.
std.listsgeneric helpers over slices and Lists, written in Nexium.
std.netTCP and UDP with addresses, written in Nexium over the net.*
std.numinteger utilities, written in Nexium.
std.processrun programs and capture what they print, written in Nexium
std.regexregular expressions without backtracking, written in Nexium.
std.streambuffered readers and writers over files and the standard
std.stringstext utilities on []u8 and String, written in Nexium.
std.testingconveniences for test blocks, written in Nexium.
std.textUTF-8 text by code point, written in Nexium.
std.threadthreads, channels and mutexes, written in Nexium over the
std.timedates, durations and timers, written in Nexium.

std.args#

std.args: command-line argument parsing, written in Nexium. import std.args then: var p = args.Parser.new(os.args()) let verbose = p.flag("--verbose", "-v") let level = p.option("--level", "-l") orelse "info" let files = p.rest() Long options take --name value or --name=value; short ones -n value. Everything after -- is positional. Unknown options stay in rest() so the caller can report them.

Types: Parser

functionwhat it does
(method) new(argv: [][]u8) -> ParserWrap os.args(); the program name in argv[0] is skipped.
(method) flag(self: *mut Self, long: []u8, short: []u8) -> boolTrue when the flag is present (any number of times).
(method) count(self: *mut Self, long: []u8, short: []u8) -> usizeHow many times the flag appears (-vvv counts as three).
(method) option(self: *mut Self, long: []u8, short: []u8) -> ?[]u8The value of --name value, --name=value, or -n value; the last one wins.
(method) options(self: *mut Self, long: []u8, short: []u8) -> List([]u8)Every value of a repeatable option, in order.
(method) int_option(self: *mut Self, long: []u8, short: []u8) -> !?i64An option parsed as an integer; error.InvalidInput when present but not a number.
(method) rest(self: *Self) -> List([]u8)Arguments not consumed by any query, plus everything after --.
(method) unknown_options(self: *Self) -> List([]u8)Unconsumed arguments that look like options: the ones the program did not ask for.
usage(program: []u8, summary: []u8, rows: [][]u8) -> StringRender a usage line and option table from (flags, description) rows.
env_map() -> Map(String, String)The environment as a map, from os.environ().

std.bytes#

std.bytes: encodings and byte-level utilities, written in Nexium. import std.bytes then bytes.hex(data), bytes.base64(data), ... Decoders return error.InvalidInput on malformed text.

functionwhat it does
hex(data: []u8) -> StringLower-case hexadecimal, two characters per byte.
unhex(text: []u8) -> !StringBytes from hexadecimal text (either case, even length).
base64(data: []u8) -> StringStandard base64 with = padding.
unbase64(text: []u8) -> !StringBytes from standard base64 (padding optional).
fnv1a(data: []u8) -> u32FNV-1a, 32 bits: a fast non-cryptographic hash.
crc32(data: []u8) -> u32CRC-32 (IEEE), as used by zip and PNG.
read_u32_be(data: []u8, at: usize) -> u32Big-endian 32-bit read.
read_u32_le(data: []u8, at: usize) -> u32Little-endian 32-bit read.
write_u32_be(out: *mut String, v: u32)Append a big-endian 32-bit value.
write_u32_le(out: *mut String, v: u32)Append a little-endian 32-bit value.
first_difference(a: []u8, b: []u8) -> ?usizeBytes that differ, for a compact diff of two buffers.

std.fs#

std.fs: files, directories and paths, written in Nexium. import std.fs then: if fs.exists("notes.txt") { ... } try fs.make_dirs("out/logs") for name in try fs.list("out") { ... } for path in try fs.walk("src") { ... } // every file, recursively let cfg = fs.join(fs.parent(argv0), "app.toml") The platform calls are the io.* builtins (documented in the language reference); this module adds paths, sorted listings, recursive create and remove, and a walker. Paths are byte strings; / and \ both separate components on every platform, and results use / unless the input used \.

functionwhat it does
exists(path: []u8) -> boolIs there a file or directory at path?
is_file(path: []u8) -> boolIs path an existing regular file (anything that is not a directory)?
is_dir(path: []u8) -> boolIs path an existing directory?
size(path: []u8) -> !u64The size of a file in bytes.
modified(path: []u8) -> !i64The modification time in milliseconds since the epoch.
read(path: []u8) -> !StringThe whole file as a String.
read_lines(path: []u8) -> !List(String)The lines of a file, without their line endings.
write(path: []u8, data: []u8) -> !voidWrite (replace) a file.
append(path: []u8, data: []u8) -> !voidAppend to a file, creating it when missing.
copy(from: []u8, to: []u8) -> !voidCopy a file's contents to a new path (the destination is replaced).
list(path: []u8) -> !List(String)The names in a directory, sorted, without . and ...
make_dir(path: []u8) -> !voidCreate one directory; fine when it already exists.
make_dirs(path: []u8) -> !voidCreate a directory and every missing parent.
remove(path: []u8) -> !voidRemove a file or an empty directory.
remove_all(path: []u8) -> !voidRemove a file, or a directory with everything in it.
rename(from: []u8, to: []u8) -> !voidRename or move a file or directory (an existing destination file is replaced).
walk(root: []u8) -> !List(String)directory by directory. Directories themselves are not listed.
cwd() -> !StringThe current working directory.
temp_dir() -> StringThe directory for temporary files.
temp_path(prefix: []u8) -> Stringdoes not exist yet. The caller creates it.
is_absolute(path: []u8) -> boolDoes the path start at a root (/x, C:\x, C:/x, \\server)?
join(dir: []u8, name: []u8) -> Stringreplaces dir.
parent(path: []u8) -> []u8/c.txt -> /.
base_name(path: []u8) -> []u8The last component: a/b/c.txt -> c.txt.
extension(path: []u8) -> []u8The extension without the dot: a/b.tar.gz -> gz, Makefile -> ``.
stem(path: []u8) -> []u8The base name without its extension: a/b.tar.gz -> b.tar.
with_extension(path: []u8, ext: []u8) -> StringThe path with its extension replaced (or added): a/b.txt, md -> a/b.md.
normalize(path: []u8) -> Stringa/./b/../c//d -> a/c/d. A leading .. is kept.

std.http#

std.http: an HTTP/1.1 client and a small server, written in Nexium over std.net and std.stream. import std.http then: let r = try http.get("http://example.com/") println("{} {}", .{r.status, r.body.len}) if let ct = r.header("content-type") { ... } fn hello(req: *http.Request) -> http.Response { return http.text(200, "hello from Nexium") } var router = http.Router.new() router.get("/", hello) var server = try http.Server.bind("127.0.0.1", 8080) try server.serve(&router) // forever, one request at a time The client speaks HTTP/1.1 with Connection: close, reads bodies by Content-Length, chunked encoding, or until close, and follows up to five redirects. Plain http:// only; TLS needs a C library through @cImport. The server handles one connection at a time, which is what a tool, a local dashboard or a test needs; threads come later in the roadmap.

Types: Header, Url, Response, Request, Route, Router, Server

functionwhat it does
parse_url(s: []u8) -> ?UrlParse http://host[:port][/path]; null for anything else.
(method) header(self: *Self, name: []u8) -> ?[]u8A header value, case-insensitive; null when absent.
(method) with_header(self: *mut Self, name: []u8, value: []u8)Add or replace a header (builder style).
(method) ok(self: *Self) -> bool
reason_for(status: u16) -> []u8The standard reason phrase for a status.
respond(status: u16, content_type: []u8, body: []u8) -> ResponseA response with a body and a content type.
text(status: u16, body: []u8) -> Response
html(status: u16, body: []u8) -> Response
json(status: u16, body: []u8) -> Response
not_found() -> Response
redirect(location: []u8) -> ResponseA redirect to location.
content_type_for(path: []u8) -> []u8The content type for a file name, by extension.
read_response(r: *mut stream.Reader) -> !ResponseRead a full response from a reader over the connection.
send_request(w: *mut stream.Writer, method: []u8, url: *Url, headers: *List(Header), body: []u8) -> !voidWrite a request; headers may add or override the defaults.
request(method: []u8, url_text: []u8, headers: *List(Header), body: []u8) -> !Responsethis client cannot speak (including https://).
get(url: []u8) -> !Response
post(url: []u8, content_type: []u8, body: []u8) -> !Response
(method) header(self: *Self, name: []u8) -> ?[]u8
(method) param(self: *Self, name: []u8) -> ?[]u8The value of a query parameter (?a=1&b=2), not decoded.
read_request(r: *mut stream.Reader, peer: []u8) -> !?Requestconnection was closed before a request line.
write_response(w: *mut stream.Writer, resp: *Response) -> !voidWrite a response with Content-Length and Connection: close.
(method) new() -> Router
(method) route(self: *mut Self, method: []u8, path: []u8, handler: fn(*Request) -> Response)
(method) get(self: *mut Self, path: []u8, handler: fn(*Request) -> Response)
(method) post(self: *mut Self, path: []u8, handler: fn(*Request) -> Response)
(method) serve_static(self: *mut Self, root: []u8)Serve files under root for paths no route claims.
(method) handle(self: *Self, req: *Request) -> ResponseThe response for a request.
static_file(root: []u8, path: []u8) -> Responsedirectories.
(method) bind(host: []u8, port: u16) -> !Server
(method) port(self: *Self) -> !u16
(method) serve_one(self: *Self, router: *Router, timeout_ms: i64) -> !voidwhen nobody connects within timeout_ms (0 waits forever).
(method) serve(self: *Self, router: *Router) -> !voidServe forever, one request at a time.
(method) close(self: *mut Self)

std.json#

std.json: a JSON parser and serializer, written in Nexium. import std.json then json.parse(text), json.stringify(&value). Values are the Json enum below; arrays and objects own their children. Numbers are f64 (JSON has one number type); integers up to 2^53 round trip.

Types: Member, Json

functionwhat it does
null_value() -> Json
boolean(b: bool) -> Json
number(n: f64) -> Json
string(s: []u8) -> Json
array() -> Json
object() -> Json
push(v: *mut Json, own item: Json)Append to an array; does nothing when v is not an array.
set(v: *mut Json, key: []u8, own value: Json)Set a key on an object (replacing an existing one); does nothing otherwise.
get(v: *Json, key: []u8) -> ?*JsonThe member key of an object, or null.
get_mut(v: *mut Json, key: []u8) -> ?*mut JsonThe member key of an object, mutable, or null.
at_mut(v: *mut Json, i: usize) -> ?*mut JsonElement i of an array, mutable, or null.
at(v: *Json, i: usize) -> ?*JsonElement i of an array, or null.
len(v: *Json) -> usizeNumber of elements or members; 0 for scalars.
is_null(v: *Json) -> bool
as_bool(v: *Json) -> ?bool
as_num(v: *Json) -> ?f64
as_str(v: *Json) -> ?[]u8
keys(v: *Json) -> List([]u8)Keys of an object in order; empty for anything else.
parse(text: []u8) -> !JsonParse a JSON document. Trailing whitespace is allowed, anything else is an error.
stringify(v: *Json) -> StringCompact text: no whitespace.
pretty(v: *Json, indent: usize) -> StringIndented text, indent spaces per level.

std.lists#

std.lists: generic helpers over slices and Lists, written in Nexium. import std.lists then lists.sum(xs), lists.map(f, xs), ... Functions take slices, so arrays, Lists, and slices all work; results that are new collections are returned as owned Lists.

functionwhat it does
sum(comptime T: type, xs: []T) -> TSum of the elements.
min(comptime T: type where T: Ord, xs: []T) -> ?TSmallest element, or null when empty.
max(comptime T: type where T: Ord, xs: []T) -> ?TLargest element, or null when empty.
arg_max(comptime T: type where T: Ord, xs: []T) -> ?usizePosition of the largest element, or null when empty.
all(comptime T: type, xs: []T, pred: fn(T) -> bool) -> boolTrue when every element satisfies pred.
any(comptime T: type, xs: []T, pred: fn(T) -> bool) -> boolTrue when some element satisfies pred.
count_if(comptime T: type, xs: []T, pred: fn(T) -> bool) -> usizeHow many elements satisfy pred.
filter(comptime T: type, xs: []T, pred: fn(T) -> bool) -> List(T)The elements that satisfy pred, in order.
map(comptime T: type, comptime U: type, xs: []T, f: fn(T) -> U) -> List(U)f applied to every element.
fold(comptime T: type, comptime A: type, xs: []T, init: A, f: fn(A, T) -> A) -> ALeft fold: f(f(f(init, x0), x1), x2).
find(comptime T: type, xs: []T, pred: fn(T) -> bool) -> ?TFirst element satisfying pred, or null.
position(comptime T: type, xs: []T, pred: fn(T) -> bool) -> ?usizePosition of the first element satisfying pred, or null.
reversed(comptime T: type, xs: []T) -> List(T)A reversed copy.
dedup(comptime T: type where T: Eq, xs: []T) -> List(T)Adjacent duplicates removed (sort first for global dedup).
take(comptime T: type, xs: []T, n: usize) -> []TThe first n elements (or all when shorter).
drop(comptime T: type, xs: []T, n: usize) -> []TEverything after the first n elements.
window_starts(len: usize, size: usize) -> List(usize)Consecutive windows of size, as start indices; for i in windows(xs, 3) then xs[i..i+3].
zip(comptime A: type, comptime B: type, a: []A, b: []B) -> List((A, B))Pairs (a[i], b[i]) up to the shorter length.
repeat(comptime T: type, xs: []T, times: usize) -> List(T)Elements repeated times times in sequence.
starts_with(comptime T: type where T: Eq, xs: []T, prefix: []T) -> boolTrue when xs starts with prefix.

std.net#

std.net: TCP and UDP with addresses, written in Nexium over the net.* primitives. import std.net then: var c = try net.TcpStream.connect("example.com", 80) try c.send("GET / HTTP/1.0\r\nHost: example.com\r\n\r\n") let reply = try c.recv_all() // until the peer closes c.close() var l = try net.TcpListener.bind("127.0.0.1", 8080) while true { var conn = try l.accept() var r = conn.reader() // a std.stream Reader let line = try r.read_line() try conn.send("ok\n") conn.close() } var u = try net.UdpSocket.bind("0.0.0.0", 0) try u.send_to("127.0.0.1", 9000, "ping") let d = try u.recv_from(1500) // d.data, d.from Every call blocks; timeouts are per socket (set_timeout, milliseconds, 0 waits forever) and expire with error.Timeout. recv returns an empty String when the peer has closed. Errors: NotFound (name lookup), ConnectionRefused, Timeout, IoError.

Types: Addr, TcpStream, TcpListener, Datagram, UdpSocket

functionwhat it does
parse_addr(s: []u8) -> ?AddrSplit host:port or [v6]:port; null when there is no valid port.
port_of(s: []u8) -> ?u16The port at the end of host:port, or null.
resolve(host: []u8) -> !List(String)The addresses a name resolves to, numeric, in resolver order.
(method) connect(host: []u8, port: u16) -> !TcpStreamConnect with a 10 second timeout.
(method) connect_timeout(host: []u8, port: u16, timeout_ms: i64) -> !TcpStreamConnect; timeout_ms 0 waits as long as the OS does.
(method) from_socket(sock: i64) -> TcpStreamWrap a socket from net.accept or net.connect.
(method) set_timeout(self: *mut Self, ms: i64)The receive timeout in milliseconds; 0 waits forever.
(method) send(self: *Self, data: []u8) -> !void
(method) recv(self: *Self, n: usize) -> !StringUp to n bytes; empty when the peer has closed.
(method) recv_all(self: *Self) -> !StringEverything until the peer closes.
(method) peer(self: *Self) -> !StringThe remote address as ip:port.
(method) local(self: *Self) -> !StringThe local address as ip:port.
(method) reader(self: *Self) -> stream.ReaderA buffered reader over the socket (lines, chunks); does not own it.
(method) writer(self: *Self) -> stream.WriterA buffered writer over the socket; flush it before waiting for a reply.
(method) close(self: *mut Self)
(method) bind(host: []u8, port: u16) -> !TcpListenerBind and listen; port 0 picks a free port (see local).
(method) local(self: *Self) -> !StringThe bound address as ip:port.
(method) port(self: *Self) -> !u16The bound port.
(method) accept(self: *Self) -> !TcpStreamWait for a connection.
(method) accept_timeout(self: *Self, timeout_ms: i64) -> !TcpStreamWait up to timeout_ms for a connection (error.Timeout otherwise).
(method) close(self: *mut Self)
(method) bind(host: []u8, port: u16) -> !UdpSocketBind; port 0 picks a free port.
(method) set_timeout(self: *mut Self, ms: i64)
(method) local(self: *Self) -> !String
(method) port(self: *Self) -> !u16
(method) send_to(self: *Self, host: []u8, port: u16, data: []u8) -> !void
(method) recv_from(self: *Self, n: usize) -> !DatagramOne datagram of at most n bytes.
(method) close(self: *mut Self)

std.num#

std.num: integer utilities, written in Nexium. import std.num then num.gcd(12, 18), num.clamp(x, 0, 10), ... The math namespace (sqrt, sin, pow on floats, ...) is a compiler builtin; this module covers what is naturally integer work.

functionwhat it does
gcd(a: u64, b: u64) -> u64Greatest common divisor (Euclid); gcd(0, 0) is 0.
lcm(a: u64, b: u64) -> u64Least common multiple; lcm(0, n) is 0.
clamp(x: i64, lo: i64, hi: i64) -> i64x limited to lo..=hi.
abs_diff(a: i64, b: i64) -> u64a - bwithout overflow on the way.
pow(base: u64, exp: u32) -> ?u64base to the exp, by squaring; null on overflow.
isqrt(n: u64) -> u64Integer square root: the largest r with r * r <= n.
is_prime(n: u64) -> boolTrial division; fine for the sizes people type by hand.
factors(n: u64) -> List(u64)The prime factors of n with multiplicity, ascending.
digits(n: u64) -> List(u8)Decimal digits of n, most significant first.
digit_sum(n: u64) -> u64Sum of the decimal digits.
to_base(n: u64, base: u64) -> Stringn in base 2..36, upper-case digits.
from_base(text: []u8, base: u64) -> ?u64Parse in base 2..36 (either case); null on an invalid digit or overflow.
round_up(n: u64, m: u64) -> u64Round up to a multiple of m (m > 0).
is_power_of_two(n: u64) -> boolTrue for 1, 2, 4, 8, ...
next_power_of_two(n: u64) -> u64The smallest power of two >= n (n <= 2^63).
popcount(n: u64) -> u32Number of set bits.

std.process#

std.process: run programs and capture what they print, written in Nexium over the process.* primitives. import std.process then: let out = try process.run(["git", "status", "--short"]) if out.ok() { print("{}", .{out.stdout}) } let r = try process.run_with(["sort"], process.Options{ .stdin = "b\na\n", .cwd = "" }) let sh = try process.shell("echo hi") // cmd /C on Windows, sh -c elsewhere The child inherits the environment. error.IoError when the program cannot be started; a non-zero exit is reported in code, not as an error. Output is read after stdin is fully written, so a program that produces more than a megabyte of output before reading its input can stall; feed such programs through files.

Types: Output, Options

functionwhat it does
(method) ok(self: *Self) -> bool
(method) text(self: *Self) -> []u8stdout without a trailing newline.
run_with(argv: [][]u8, opts: Options) -> !Output
run(argv: [][]u8) -> !OutputRun and capture, inheriting the working directory, with no stdin.
shell(command: []u8) -> !OutputRun a command line through the platform shell.

std.regex#

std.regex: regular expressions without backtracking, written in Nexium. import std.regex then: let re = try regex.compile("(\\w+)@(\\w+)\\.com") if re.is_match(text) { ... } if let m = re.find(text) { println("{} at {}", .{m.text(), m.start}) } for m in re.find_all(text) { println("{}", .{m.group(1).?}) } let out = re.replace_all(text, "$2:$1") let parts = try regex.compile(",\\s") for p in parts.split("a, b,c") { ... } Syntax: literals, . (any byte but newline), classes [a-z] [^...], \d \w \s \D \W \S \b \B, escapes \. \\ \n \t \r, anchors ^ $, groups (...) and (?:...), alternation |, repeats ` + ? {n} {n,} {n,m}and their lazy forms*? +? ??`. Matching is a Pike VM (Thompson's NFA simulation), so every search is linear in the text and the pattern; there are no back-references. Patterns and text are bytes.

Types: Regex, Match

functionwhat it does
compile(pattern: []u8) -> !RegexCompile a pattern; error.InvalidInput when it is malformed.
is_match(pattern: []u8, text: []u8) -> !boolDoes pattern match anywhere in text? (Compiles every call.)
(method) text(self: *Self) -> []u8The matched bytes.
(method) group(self: *Self, i: usize) -> ?[]u8Group i (0 is the whole match); null when the group did not participate.
(method) group_count(self: *Self) -> usizeThe number of groups, counting group 0.
(method) find_at(self: *Self, text: []u8, from: usize) -> ?MatchThe first match at or after byte from.
(method) find(self: *Self, text: []u8) -> ?MatchThe first match in text.
(method) is_match(self: *Self, text: []u8) -> boolDoes the pattern match anywhere in text?
(method) find_all(self: *Self, text: []u8) -> List(Match)Every non-overlapping match, left to right.
(method) replace_all(self: *Self, text: []u8, repl: []u8) -> Stringdollar sign.
(method) split(self: *Self, text: []u8) -> List([]u8)The pieces of text between matches.

std.stream#

std.stream: buffered readers and writers over files and the standard streams, written in Nexium. import std.stream then: var r = try stream.Reader.open("big.log") while true { // no whole-file allocation let line = (try r.read_line()) orelse break ... } r.close() var w = try stream.Writer.open("out.txt") try w.write_line("hello") try w.close() // flushes, then closes var input = stream.Reader.stdin() var out = stream.Writer.stdout() try stream.copy(&mut input, &mut out) try out.flush() Readers buffer 64 KB at a time; writers gather output and flush when the buffer fills, on flush, and on close. A Writer must be flushed or closed before the program ends, or buffered output is lost.

Types: Reader, Writer

functionwhat it does
(method) open(path: []u8) -> !ReaderOpen a file for reading.
(method) stdin() -> ReaderStandard input.
(method) from_handle(handle: i64) -> ReaderWrap a handle from io.open; close will not close it.
(method) from_socket(sock: i64) -> Readerwill not close it.
(method) read_line(self: *mut Self) -> !?StringThe next line without its \n (or \r\n); null at end of input.
(method) read(self: *mut Self, n: usize) -> !StringUp to n bytes; empty at end of input.
(method) read_all(self: *mut Self) -> !StringEverything that is left.
(method) close(self: *mut Self)Release the file (the standard streams stay open).
(method) open(path: []u8) -> !WriterCreate or replace a file.
(method) append(path: []u8) -> !WriterOpen a file for appending.
(method) stdout() -> Writer
(method) stderr() -> Writer
(method) from_handle(handle: i64) -> WriterWrap a handle from io.open; close will not close it.
(method) from_socket(sock: i64) -> WriterWrap a connected socket; close will not close it.
(method) write(self: *mut Self, data: []u8) -> !void
(method) write_line(self: *mut Self, data: []u8) -> !void
(method) flush(self: *mut Self) -> !voidHand buffered output to the handle.
(method) close(self: *mut Self) -> !voidFlush, then release the file (the standard streams stay open).
copy(r: *mut Reader, w: *mut Writer) -> !usizeCopy everything from a reader to a writer; the number of bytes moved.

std.strings#

std.strings: text utilities on []u8 and String, written in Nexium. import std.strings then strings.join(parts, ", "). The core methods (len, split, trim, find, starts_with, parse_int, ...) are compiler builtins; this module adds what is naturally written in the language itself. Slices returned here point into the argument they were cut from; String results are owned by the caller.

functionwhat it does
join(parts: [][]u8, sep: []u8) -> StringConcatenate parts with sep between them.
repeat(s: []u8, n: usize) -> Strings repeated n times.
pad_left(s: []u8, width: usize, fill: u8) -> StringLeft-pad with fill to at least width bytes.
pad_right(s: []u8, width: usize, fill: u8) -> StringRight-pad with fill to at least width bytes.
center(s: []u8, width: usize, fill: u8) -> StringCenter in width bytes, extra fill on the right.
count(s: []u8, needle: []u8) -> usizeHow many non-overlapping times needle occurs in s.
replace(s: []u8, from: []u8, to: []u8) -> StringEvery occurrence of from replaced by to.
index_from(s: []u8, needle: []u8, start: usize) -> ?usizePosition of needle at or after start.
last_index(s: []u8, needle: []u8) -> ?usizePosition of the last occurrence of needle.
strip_prefix(s: []u8, prefix: []u8) -> ?[]u8s without a leading prefix, or null when it does not start with it.
strip_suffix(s: []u8, suffix: []u8) -> ?[]u8s without a trailing suffix, or null when it does not end with it.
trim_left(s: []u8) -> []u8Leading ASCII whitespace removed.
trim_right(s: []u8) -> []u8Trailing ASCII whitespace removed.
is_blank(s: []u8) -> boolTrue when s is empty or only ASCII whitespace.
split_whitespace(s: []u8) -> List([]u8)Split on runs of ASCII whitespace; no empty pieces.
to_upper(s: []u8) -> StringASCII letters upper-cased; other bytes unchanged.
to_lower(s: []u8) -> StringASCII letters lower-cased; other bytes unchanged.
capitalize(s: []u8) -> StringFirst ASCII letter upper-cased.
reverse(s: []u8) -> StringBytes in reverse order (bytes, not code points).
split_once(s: []u8, sep: []u8) -> ?([]u8, []u8)Cut at the first sep: (before, after), or null when sep is absent.
ellipsize(s: []u8, max: usize) -> StringTruncate to max bytes, appending ... when something was cut.

std.testing#

std.testing: conveniences for test blocks, written in Nexium. import std.testing then, inside a test: testing.expect_approx(area, 3.14159, 0.001) testing.expect_err(i32, parse("nope")) testing.expect_contains(output, "42 items") testing.expect_lines(rendered, expected) // reports the first differing line try testing.snapshot("report", rendered) // compares to snapshots/report.txt Snapshots live in snapshots/<name>.txt under the current directory. A missing file is written and the test passes; a mismatch fails with the first differing line. Set NX_UPDATE_SNAPSHOTS=1 to rewrite them all.

functionwhat it does
approx(a: f64, b: f64, eps: f64) -> boolAre two floats within eps of each other?
expect_approx(a: f64, b: f64, eps: f64)Panics unless a and b are within eps.
is_err(comptime T: type, own r: !T) -> boolDid the call fail? (Any error counts.)
expect_err(comptime T: type, own r: !T)Panics unless the result is an error.
expect_error(comptime T: type, own r: !T, err: error)Panics unless the result is exactly err.
expect_contains(hay: []u8, needle: []u8)Panics unless hay contains needle.
expect_lines(actual: []u8, expected: []u8)Compares line by line; panics naming the first line that differs.
snapshot_in(dir: []u8, name: []u8, actual: []u8) -> !voidNX_UPDATE_SNAPSHOTS is set.
snapshot(name: []u8, actual: []u8) -> !voidsnapshot_in("snapshots", name, actual).

std.text#

std.text: UTF-8 text by code point, written in Nexium. import std.text then: let n = text.char_count("héllo") // 5, not 6 for cp in text.chars("héllo") { ... } // code points as u32 let w = text.width("日本語") // 6 columns on a terminal let s = text.to_upper("straße") // "STRASSE" is not attempted: "STRAßE" let t = text.truncate("héllo wörld", 5) // "héllo", never mid-character let ok = text.is_valid("...") Strings are bytes; this module reads them as UTF-8, tolerating bad input (an invalid byte decodes as U+FFFD and advances one byte). Case mapping covers ASCII, Latin-1, Latin Extended-A, Greek and Cyrillic, which is what is cheap to do without tables. width follows the usual terminal convention: East Asian wide and fullwidth forms take two columns, combining marks and zero-width characters take none.

Types: Decoded

functionwhat it does
decode_at(s: []u8, i: usize) -> DecodedU+FFFD with length 1 so callers always make progress.
push(out: *mut String, cp: u32)Append a code point as UTF-8.
encode(cp: u32) -> StringA code point as a String.
is_valid(s: []u8) -> boolIs the text well-formed UTF-8?
chars(s: []u8) -> List(u32)All code points.
char_count(s: []u8) -> usizeThe number of code points.
byte_offset(s: []u8, n: usize) -> usizeThe byte offset of the nth code point (or s.len when past the end).
char_at(s: []u8, n: usize) -> ?u32The nth code point, or null.
slice(s: []u8, from: usize, to: usize) -> []u8Code points from (inclusive) to to (exclusive), as a slice of s.
truncate(s: []u8, n: usize) -> []u8The first n code points; never cuts a character in half.
reverse(s: []u8) -> StringThe code points in reverse order.
is_zero_width(cp: u32) -> boolDoes the code point take no columns (combining marks, zero-width, controls)?
is_wide(cp: u32) -> boolDoes the code point take two columns (East Asian wide and fullwidth)?
char_width(cp: u32) -> usizeColumns a code point takes on a terminal: 0, 1 or 2.
width(s: []u8) -> usizeColumns the text takes on a terminal.
pad_right(s: []u8, columns: usize) -> StringPad on the right to columns terminal columns (by width, not bytes).
upper_char(cp: u32) -> u32Latin-1, Latin Extended-A (pairs), Greek, Cyrillic.
lower_char(cp: u32) -> u32Lower-case a code point; the inverse of upper_char.
to_upper(s: []u8) -> String
to_lower(s: []u8) -> String
eq_ignore_case(a: []u8, b: []u8) -> boolCompare ignoring case, using the same mapping as to_lower.

std.thread#

std.thread: threads, channels and mutexes, written in Nexium over the thread.* and sync.* primitives. import std.thread then: fn work(job: *mut Job) -> i64 { ... } var t = thread.spawn(Job, i64, work, Job{ .from = 0, .to = 1000 }) let total = t.join() // the function's result fn produce(p: *mut Producer) { ... } // no result: a Worker var ch = thread.channel(String) // shared by pointer var producer = thread.run(Producer, produce, Producer{ .out = &mut ch }) let msg = ch.recv() orelse break // null once closed and drained producer.join() var counter = thread.mutex(i64, 0) let n = counter.lock() // mut i64 while held n. += 1 counter.unlock() A thread function takes a pointer to its argument, which the Thread owns until join returns the result. Channels and mutexes are values that threads share by pointer; the owner must join every thread using them before letting them go out of scope, and call free when done. Panics inside a thread surface from join.

Types: Task(T,, Thread(T,, WorkerTask(T){, Worker(T){, Channel(T){, Mutex(T){

functionwhat it does
spawn(comptime T: type, comptime R: type, f: fn(*mut T) -> R, own arg: T) -> Thread(T, R)Run f(&mut arg) on a new thread.
(method) join(self: *mut Self) -> RWait for the thread and take its result. Joining twice panics.
(method) arg(self: *Self) -> *TThe argument after the thread finished (for results written in place).
run(comptime T: type, f: fn(*mut T) -> void, own arg: T) -> Worker(T)Run f(&mut arg) on a new thread, for functions without a result.
(method) join(self: *mut Self)Wait for the thread. Joining twice panics.
(method) arg(self: *Self) -> *TThe argument after the thread finished (for results written in place).
count() -> usizeThe number of hardware threads.
channel(comptime T: type) -> Channel(T)
(method) send(self: *mut Self, own value: T)
(method) recv(self: *mut Self) -> ?TThe next value, waiting for one; null when closed and empty.
(method) try_recv(self: *mut Self) -> ?TThe next value if one is queued, without waiting.
(method) close(self: *mut Self)No more values will be sent; receivers drain what is left, then see null.
(method) len(self: *mut Self) -> usize
(method) free(self: *mut Self)Release the lock and condition variable; after every user has stopped.
mutex(comptime T: type, own value: T) -> Mutex(T)
(method) lock(self: *mut Self) -> *mut TTake the lock; the pointer is valid until unlock.
(method) unlock(self: *mut Self)
(method) free(self: *mut Self)Release the lock; after every user has stopped.

std.time#

std.time: dates, durations and timers, written in Nexium. import std.time then: let now = time.now_utc() // a DateTime println("{}", .{now.iso()}) // 2026-09-19T04:15:14.123Z println("{}", .{now.format("%Y-%m-%d %H:%M")}) let local = time.now_local() // with the machine's UTC offset let d = time.Duration.minutes(90) println("{}", .{d.text()}) // 1h 30m var sw = time.Stopwatch.start() ... work ... println("took {}", .{sw.elapsed().text()}) Instants are milliseconds since 1970-01-01T00:00:00Z (time.now()), as i64; negative values are before the epoch. Calendar arithmetic is the proleptic Gregorian calendar; the only platform call is the local UTC offset, and only now_local and local use it.

Types: DateTime, Duration, Stopwatch

functionwhat it does
is_leap(year: i32) -> bool
days_in_month(year: i32, month: u8) -> u8
utc(ms: i64) -> DateTimeBreak an instant down in UTC.
local(ms: i64) -> DateTimeBreak an instant down in the machine's local time zone.
with_offset(ms: i64, offset_min: i32) -> DateTimeBreak an instant down at a fixed offset in minutes east of UTC.
now_utc() -> DateTimeThe current instant, in UTC.
now_local() -> DateTimeThe current instant, in local time.
date(year: i32, month: u8, day: u8) -> ?DateTimeA date at midnight UTC; null when the fields do not name a real day.
(method) to_ms(self: *Self) -> i64Milliseconds since the epoch (the offset is subtracted back out).
(method) to_utc(self: *Self) -> DateTimeThe same instant expressed in UTC.
(method) weekday(self: *Self) -> u8Day of the week, 0 = Monday ... 6 = Sunday.
(method) day_of_year(self: *Self) -> u16Day of the year, 1-based.
(method) date_text(self: *Self) -> StringYYYY-MM-DD.
(method) time_text(self: *Self) -> StringHH:MM:SS.
(method) offset_text(self: *Self) -> StringThe offset as Z, or +HH:MM / -HH:MM.
(method) iso(self: *Self) -> StringISO 8601 / RFC 3339: 2026-09-19T04:15:14.123Z, ...+02:00.
(method) format(self: *Self, spec: []u8) -> StringUnknown letters are copied through.
parse_iso(s: []u8) -> ?DateTimetext is not a date.
(method) millis(n: i64) -> Duration
(method) seconds(n: i64) -> Duration
(method) minutes(n: i64) -> Duration
(method) hours(n: i64) -> Duration
(method) days(n: i64) -> Duration
(method) between(a: i64, b: i64) -> DurationThe span from a to b (instants in ms).
(method) since(ms: i64) -> DurationThe span from an instant to now.
(method) total_seconds(self: *Self) -> f64
(method) total_minutes(self: *Self) -> f64
(method) total_hours(self: *Self) -> f64
(method) plus(self: *Self, other: Duration) -> Duration
(method) minus(self: *Self, other: Duration) -> Duration
(method) text(self: *Self) -> StringHuman text: 250ms, 3.5s, 2m 05s, 1h 02m, 3d 04h.
add(ms: i64, d: Duration) -> i64instant + duration.
(method) start() -> Stopwatch
(method) elapsed_ms(self: *Self) -> f64Elapsed time in milliseconds, fractional.
(method) elapsed(self: *Self) -> DurationElapsed time as a Duration (whole milliseconds).
(method) lap(self: *mut Self) -> DurationRestart and return what had elapsed.