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.
| module | what |
|---|---|
std.args |
command-line argument parsing, written in Nexium. |
std.base64 |
base64 (RFC 4648), written in Nexium: the standard alphabet |
std.bytes |
encodings and byte-level utilities, written in Nexium. |
std.csv |
comma-separated values (RFC 4180), written in Nexium: reading |
std.deque |
a double-ended queue, written in Nexium: two Lists back to |
std.env |
the environment, and where a program keeps its files, written in |
std.fs |
files, directories and paths, written in Nexium. |
std.hash |
hash functions, written in Nexium: FNV-1a (64-bit), SipHash-2-4 |
std.heap |
a priority queue, written in Nexium: a binary heap over a List, |
std.http |
an HTTP/1.1 client and a small server, written in Nexium over |
std.json |
a JSON parser and serializer, written in Nexium. |
std.lists |
generic helpers over slices and Lists, written in Nexium. |
std.log |
leveled, structured logging, written in Nexium: a message and |
std.net |
TCP and UDP with addresses, written in Nexium over the net.* |
std.num |
integer utilities, written in Nexium. |
std.path |
paths as text, written in Nexium: joining, splitting, comparing |
std.process |
run programs and capture what they print, or talk to them |
std.regex |
regular expressions without backtracking, written in Nexium. |
std.set |
a set of values, written in Nexium over Map(T, bool): its |
std.sort |
sorting by a comparison of your own, stable sorting, and |
std.stream |
buffered readers and writers over files and the standard |
std.strings |
text utilities on []u8 and String, written in Nexium. |
std.testing |
conveniences for test blocks, written in Nexium. |
std.text |
UTF-8 text by code point and by grapheme cluster, written in |
std.thread |
threads, channels, select, mutexes and atomics, written in |
std.time |
dates, durations, time zones and timers, written in Nexium. |
std.toml |
TOML 1.0 (toml.io), written in Nexium: reading a document into |
std.uuid |
UUIDs (RFC 9562), written in Nexium: random ones (version 4), |
std.websocket |
a WebSocket client (RFC 6455), written in Nexium over |
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
| function | what it does |
|---|---|
(method) new(argv: [][]u8) -> Parser |
Wrap os.args(); the program name in argv[0] is skipped. |
(method) flag(self: *mut Self, long: []u8, short: []u8) -> bool |
True when the flag is present (any number of times). |
(method) count(self: *mut Self, long: []u8, short: []u8) -> usize |
How many times the flag appears (-vvv counts as three). |
(method) option(self: *mut Self, long: []u8, short: []u8) -> ?[]u8 |
The 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) -> !?i64 |
An 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) -> String |
Render a usage line and option table from (flags, description) rows. |
env_map() -> Map(String, String) |
The environment as a map, from os.environ(). |
std.base64: base64 (RFC 4648), written in Nexium: the standard alphabet with its = padding, and the URL-safe one (- and _ for + and /) without, as JSON Web Tokens and URLs carry it. import std.base64 then: let s = base64.encode("hi!") // "aGkh" let raw = try base64.decode(s) let token = base64.encode_url(key) // no =, safe in a URL let back = try base64.decode_url(token) Decoding is strict: a character outside the alphabet, padding anywhere but at the end or more of it than the length needs, or a length no encoding produces is error.InvalidInput. std.bytes keeps the lenient reader, unbase64, which skips line breaks and stray padding.
| function | what it does |
|---|---|
encode(data: []u8) -> String |
Standard base64 with = padding. |
decode(text: []u8) -> !String |
Bytes from standard base64; the padding may be left out. |
encode_url(data: []u8) -> String |
URL-safe base64 without padding (RFC 4648 section 5, as JWTs use). |
decode_url(text: []u8) -> !String |
Bytes from URL-safe base64, padded or not. |
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.
| function | what it does |
|---|---|
hex(data: []u8) -> String |
Lower-case hexadecimal, two characters per byte. |
unhex(text: []u8) -> !String |
Bytes from hexadecimal text (either case, even length). |
base64(data: []u8) -> String |
Standard base64 with = padding. |
unbase64(text: []u8) -> !String |
Bytes from standard base64 (padding optional). |
fnv1a(data: []u8) -> u32 |
FNV-1a, 32 bits: a fast non-cryptographic hash. |
crc32(data: []u8) -> u32 |
CRC-32 (IEEE), as used by zip and PNG. |
read_u32_be(data: []u8, at: usize) -> u32 |
Big-endian 32-bit read. |
read_u32_le(data: []u8, at: usize) -> u32 |
Little-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) -> ?usize |
Bytes that differ, for a compact diff of two buffers. |
std.csv: comma-separated values (RFC 4180), written in Nexium: reading rows, reading rows by their header, and writing rows. import std.csv then: let rows = try csv.parse(text) // List(List(String)) for row in try csv.by_header(text) { // Map(String, String) let name = row["name"] orelse "?" } let tsv = try csv.parse_with(text, '\t') let out = csv.to_text(&rows) // quoted where needed, CRLF A field in double quotes may hold the separator, line breaks and "" for a quote. A quote inside an unquoted field, anything but the separator or a line end after a closing quote, and a quoted field never closed are error.InvalidInput. Lines may end in CRLF or LF, empty lines are skipped, and a leading UTF-8 byte-order mark is dropped.
| function | what it does |
|---|---|
parse(text: []u8) -> !List(List(String)) |
The rows of comma-separated text. |
parse_with(text: []u8, sep: u8) -> !List(List(String)) |
The rows of text whose fields are separated by sep ('\t' for TSV, ';' where the decimal separator is a comma). |
by_header(text: []u8) -> !List(Map(String, String)) |
The rows after the first, each as a map from the first row's names to its fields. A row shorter than the header leaves the missing names out; one longer is error.InvalidInput. |
field(value: []u8, sep: u8) -> String |
A field as written between separators: in double quotes, with any quote doubled, when it holds sep, a quote or a line break. |
to_text(rows: *List(List(String))) -> String |
Rows as comma-separated text, each line ending in CRLF. |
to_text_with(rows: *List(List(String)), sep: u8, line_end: []u8) -> String |
Rows as text with sep between fields and line_end after each row. |
std.deque: a double-ended queue, written in Nexium: two Lists back to back, the first kept reversed, so both ends push and pop in O(1) amortized (when one side runs out, half of the other moves over). import std.deque then: var q = deque.of(Job) q.push_back(job) q.push_front(urgent) let next = q.pop_front() orelse return if let last = q.last() { ... } // a view, left in place
Types: Deque(T){
| function | what it does |
|---|---|
of(comptime T: type) -> Deque(T) |
An empty deque. |
(method) push_back(self: *mut Self, own x: T) |
|
(method) push_front(self: *mut Self, own x: T) |
|
(method) pop_front(self: *mut Self) -> ?T |
Takes out the first element, or null when empty. |
(method) pop_back(self: *mut Self) -> ?T |
Takes out the last element, or null when empty. |
(method) len(self: *Self) -> usize |
|
(method) is_empty(self: *Self) -> bool |
|
(method) get(self: *Self, i: usize) -> ?*T |
The element i places from the front, left in place; null past the end. |
(method) first(self: *Self) -> ?*T |
|
(method) last(self: *Self) -> ?*T |
|
(method) clear(self: *mut Self) |
std.env: the environment, and where a program keeps its files, written in Nexium: variables, the home directory, the config, data, cache and state directories each platform expects (XDG on Linux and the BSDs, Library on macOS, AppData on Windows), and .env files. import std.env then: let port = env.get_or("PORT", "8080") let dir = env.config_dir("myapp") // ?String: /.config/myapp, ... let notes = env.expand_home("/notes.txt") let n = env.load_dotenv(".env", false) catch 0 // sets what is not set yet The directories are where a program should keep its files; nothing here creates them (fs.make_dirs does). Each is null when the variables it comes from are not set.
Types: Var
| function | what it does |
|---|---|
get(name: []u8) -> ?String |
The variable's value, or null when it is not set or set to nothing. |
get_or(name: []u8, default: []u8) -> String |
The variable's value, or default when it is not set or set to nothing. |
set(name: []u8, value: []u8) |
Set a variable for this program and the ones it starts. |
unset(name: []u8) |
Remove a variable. |
all() -> List(Var) |
Every variable, sorted by name. |
home() -> ?String |
The user's home directory: HOME, or on Windows USERPROFILE. |
expand_home(p: []u8) -> String |
~ or ~/... at the start of a path, with the home directory in its place; any other path as it is. |
config_dir(app: []u8) -> ?String |
Where a program keeps its settings, in a directory named app (none when app is empty): $XDG_CONFIG_HOME or ~/.config on Linux and the BSDs, ~/Library/Application Support on macOS, %APPDATA% on Windows. |
data_dir(app: []u8) -> ?String |
Where a program keeps what it makes and needs to keep: $XDG_DATA_HOME or ~/.local/share, ~/Library/Application Support, %APPDATA%. |
cache_dir(app: []u8) -> ?String |
Where a program keeps what it can make again: $XDG_CACHE_HOME or ~/.cache, ~/Library/Caches, %LOCALAPPDATA%. |
state_dir(app: []u8) -> ?String |
Where a program keeps its state between runs (logs, history, what was open): $XDG_STATE_HOME or ~/.local/state, ~/Library/Application Support, %LOCALAPPDATA%. |
parse_dotenv(text: []u8) -> !List(Var) |
The variables a .env file sets, in order: NAME=value lines, an optional export before the name, # comments and blank lines skipped, a value in single quotes taken as written, one in double quotes with \n, \t, \" and \\ read as escapes, and an unquoted value trimmed and cut at #. A line that is none of these is an error. |
load_dotenv(file: []u8, override: bool) -> !usize |
Read a .env file and set its variables: those not set already, or every one when override. How many were set; error.InvalidInput when a line does not parse (nothing is set then). |
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 sorted listings, recursive create and remove, and a walker. Its path functions call std.path's, which has more. Paths are byte strings; / and \ both separate components on every platform, and results use / unless the input used \.
| function | what it does |
|---|---|
exists(path: []u8) -> bool |
Is there a file or directory at path? |
is_file(path: []u8) -> bool |
Is path an existing regular file (anything that is not a directory)? |
is_dir(path: []u8) -> bool |
Is path an existing directory? |
size(path: []u8) -> !u64 |
The size of a file in bytes. |
modified(path: []u8) -> !i64 |
The modification time in milliseconds since the epoch. |
read(path: []u8) -> !String |
The 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) -> !void |
Write (replace) a file. |
append(path: []u8, data: []u8) -> !void |
Append to a file, creating it when missing. |
copy(from: []u8, to: []u8) -> !void |
Copy 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) -> !void |
Create one directory; fine when it already exists. |
make_dirs(path: []u8) -> !void |
Create a directory and every missing parent. |
remove(path: []u8) -> !void |
Remove a file or an empty directory. |
remove_all(path: []u8) -> !void |
Remove a file, or a directory with everything in it. |
rename(from: []u8, to: []u8) -> !void |
Rename or move a file or directory (an existing destination file is replaced). |
walk(root: []u8) -> !List(String) |
Every file under root, recursively, as paths joined onto root, sorted directory by directory. Directories themselves are not listed. |
cwd() -> !String |
The current working directory. |
temp_dir() -> String |
The directory for temporary files. |
temp_path(prefix: []u8) -> String |
A fresh path in the temporary directory, <temp>/<prefix><number>, that does not exist yet. The caller creates it. |
is_absolute(p: []u8) -> bool |
Does the path start at a root (/x, C:\x, C:/x, \\server)? As path.is_absolute. |
join(dir: []u8, name: []u8) -> String |
dir/name; a separator is added only when needed, and an absolute name replaces dir. As path.join. |
parent(p: []u8) -> []u8 |
Everything before the last separator: a/b/c.txt -> a/b, c.txt -> ``, /c.txt -> `/`. As `path.parent`. |
base_name(p: []u8) -> []u8 |
The last component: a/b/c.txt -> c.txt. As path.base_name. |
extension(p: []u8) -> []u8 |
The extension without the dot: a/b.tar.gz -> gz, Makefile -> ``. As path.extension. |
stem(p: []u8) -> []u8 |
The base name without its extension: a/b.tar.gz -> b.tar. As path.stem. |
with_extension(p: []u8, ext: []u8) -> String |
The path with its extension replaced (or added): a/b.txt, md -> a/b.md. As path.with_extension. |
normalize(p: []u8) -> String |
Collapse . and .. components and repeated separators: a/./b/../c//d -> a/c/d. A leading .. is kept. As path.normalize. |
std.hash: hash functions, written in Nexium: FNV-1a (64-bit), SipHash-2-4 (keyed: a table whose keys an adversary picks), SHA-256 (checksums and content addresses), and SHA-1 for the protocols that still require it. import std.hash then: let h = hash.fnv1a64(name) // fast, not keyed let k = hash.siphash(key16, name) // keyed with 16 bytes let sum = hash.sha256_hex(file_text) // 64 hex digits var s = hash.Sha256.new() // or piece by piece s.update(part1) s.update(part2) let digest = s.finish() // 32 bytes std.bytes keeps the 32-bit fnv1a and crc32.
Types: Sha256
| function | what it does |
|---|---|
fnv1a64(data: []u8) -> u64 |
FNV-1a over 64 bits. |
siphash(key: []u8, data: []u8) -> u64 |
SipHash-2-4 of data under a 16-byte key (panics on another length): what a hash table keyed by untrusted input should use. |
(method) new() -> Sha256 |
|
(method) update(self: *mut Self, data: []u8) |
|
(method) finish(self: *mut Self) -> String |
The 32-byte digest of everything given to update. |
sha256(data: []u8) -> String |
The SHA-256 digest of data: 32 bytes. |
sha256_hex(data: []u8) -> String |
The SHA-256 digest of data as 64 lower-case hex digits. |
sha1(data: []u8) -> String |
The SHA-1 digest of data: 20 bytes. SHA-1 is broken for anything an adversary can choose, so it is here for the protocols that still require it (the WebSocket handshake, std.websocket), never to check or sign data; use sha256 for that. |
sha1_hex(data: []u8) -> String |
The SHA-1 digest of data as 40 lower-case hex digits. |
std.heap: a priority queue, written in Nexium: a binary heap over a List, ordered by a comparison of your own. import std.heap then: var q = heap.by(Job, |a: *Job, b: *Job| -> bool { return a.due < b.due }) q.push(job) while true { let next = q.pop() orelse break // the least by the comparison first run(next) } push and pop are O(log n), peek O(1). A heap that pops the greatest first is one whose comparison says a > b.
Types: Heap(T){
| function | what it does |
|---|---|
by(comptime T: type, less: fn(*T, *T) -> bool) -> Heap(T) |
An empty heap ordered by less. |
(method) push(self: *mut Self, own x: T) |
Adds x. |
(method) pop(self: *mut Self) -> ?T |
Takes out the least element, or null when the heap is empty. |
(method) peek(self: *Self) -> ?*T |
The least element, left in place; null when the heap is empty. |
(method) len(self: *Self) -> usize |
|
(method) is_empty(self: *Self) -> bool |
|
(method) clear(self: *mut Self) |
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("https://example.com/") // TLS by the system's own library println("{} {}", .{r.status, r.body.len}) if let ct = r.header("content-type") { ... } var client = http.client_with(NxTls, &mut layer) // or a TLS layer of a package's client.timeout_ms = 10000 // and timeouts, redirects, limits var s = try client.open("GET", "https://example.com/big", &headers, "") while true { // the body as it arrives let piece = (try s.next()) orelse break ... } s.close() 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 over a Transport: TCP for http://, and for https:// the TLS layer a program hands its client (the slot of decision 120, which nxtls, the TLS 1.3 client written in Nexium, fills), or without one SystemTls, the platform's own: SChannel on Windows, Security.framework on macOS, OpenSSL on Linux and the BSDs, each checking the server's certificate against the system's roots and the host's name (SystemTls.problem() says why one failed). It reads a body by Content-Length, chunked encoding, or until the connection ends (refused when a TLS connection was cut rather than closed, as nothing then shows the body is whole), whole or as it arrives, follows up to five redirects, and gives up on a connect or a wait after timeout_ms. 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, Plain, SystemTls, Streaming(T){, Client(T){, Request, Route, Router, Server
| function | what it does |
|---|---|
parse_url(s: []u8) -> ?Url |
Parse http://host[:port][/path]; null for anything else. |
(method) header(self: *Self, name: []u8) -> ?[]u8 |
A 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) -> []u8 |
The standard reason phrase for a status. |
respond(status: u16, content_type: []u8, body: []u8) -> Response |
A 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) -> Response |
A redirect to location. |
content_type_for(path: []u8) -> []u8 |
The content type for a file name, by extension. |
read_response(r: *mut stream.Reader) -> !Response |
Read a full response from a reader over the connection. |
send_request(w: *mut stream.Writer, method: []u8, url: *Url, headers: *List(Header), body: []u8) -> !void |
Write a request; headers may add or override the defaults. |
(method) new() -> Plain |
|
(method) new() -> SystemTls |
|
(method) available() -> bool |
Does this system have a TLS library? Windows and macOS always do; Linux and the BSDs when OpenSSL's libssl (3 or 1.1) is installed. |
(method) problem() -> String |
Why the last TLS connection or call on this thread failed, in words: "the server's certificate has expired", "no such host". |
(method) header(self: *Self, name: []u8) -> ?[]u8 |
A header value, case-insensitive; null when absent. |
(method) ok(self: *Self) -> bool |
|
(method) next(self: *mut Self) -> !?String |
The next piece of the body; null once all of it has come. error.Truncated when the connection ends before the body does. |
(method) read_all(self: *mut Self) -> !String |
The rest of the body at once. |
(method) close(self: *mut Self) |
Closes the connection. |
client() -> Client(Plain) |
A client for http://, and for https:// over the system's TLS (SystemTls). |
client_with(comptime T: type where T: Transport, tls: *mut T) -> Client(T) |
A client whose https:// goes over tls, a TLS layer: a Transport, such as nxtls's. |
(method) open(self: *mut Self, method: []u8, url_text: []u8, headers: *List(Header), body: []u8) -> !Streaming(T) |
Sends a request and reads the response's status and headers, following redirects: a 303, and a 301 or 302 to a POST, turn into a GET without the body, and a redirect to another host goes without the Authorization and Cookie headers. The body is read from the Streaming as it arrives; close it when done. |
(method) send(self: *mut Self, method: []u8, url: []u8, headers: *List(Header), body: []u8) -> !Response |
A request, its response read whole, following redirects. |
(method) get(self: *mut Self, url: []u8) -> !Response |
|
(method) post(self: *mut Self, url: []u8, content_type: []u8, body: []u8) -> !Response |
|
request(method: []u8, url_text: []u8, headers: *List(Header), body: []u8) -> !Response |
Perform a request with a client(), following redirects: https:// over the system's TLS. error.InvalidInput for a URL that is not http or https, error.Unsupported for https:// on a system without a TLS library. |
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) -> ?[]u8 |
The value of a query parameter (?a=1&b=2), not decoded. |
read_request(r: *mut stream.Reader, peer: []u8) -> !?Request |
Read a request from a reader over the connection; null when the connection was closed before a request line. |
read_request_max(r: *mut stream.Reader, peer: []u8, max_body: usize) -> !?Request |
read_request with a body limit of max_body bytes (0: none). |
write_response(w: *mut stream.Writer, resp: *Response) -> !void |
Write 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) -> Response |
The response for a request. |
static_file(root: []u8, path: []u8) -> Response |
A file under root for a request path, refusing ..; index.html for directories. |
(method) bind(host: []u8, port: u16) -> !Server |
|
(method) port(self: *Self) -> !u16 |
|
(method) serve_one(self: *Self, router: *Router, timeout_ms: i64) -> !void |
Accept one connection, answer one request, close. error.Timeout when nobody connects within timeout_ms (0 waits forever). |
(method) serve(self: *Self, router: *Router) -> !void |
Serve forever, one request at a time. |
(method) close(self: *mut Self) |
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
| function | what 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) -> ?*Json |
The member key of an object, or null. |
get_mut(v: *mut Json, key: []u8) -> ?*mut Json |
The member key of an object, mutable, or null. |
at_mut(v: *mut Json, i: usize) -> ?*mut Json |
Element i of an array, mutable, or null. |
at(v: *Json, i: usize) -> ?*Json |
Element i of an array, or null. |
len(v: *Json) -> usize |
Number 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) -> !Json |
Parse a JSON document. Trailing whitespace is allowed, anything else is an error. |
stringify(v: *Json) -> String |
Compact text: no whitespace. |
pretty(v: *Json, indent: usize) -> String |
Indented text, indent spaces per level. |
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.
| function | what it does |
|---|---|
sum(comptime T: type, xs: []T) -> T |
Sum of the elements. |
min(comptime T: type where T: Ord, xs: []T) -> ?T |
Smallest element, or null when empty. |
max(comptime T: type where T: Ord, xs: []T) -> ?T |
Largest element, or null when empty. |
arg_max(comptime T: type where T: Ord, xs: []T) -> ?usize |
Position of the largest element, or null when empty. |
all(comptime T: type, xs: []T, pred: fn(T) -> bool) -> bool |
True when every element satisfies pred. |
any(comptime T: type, xs: []T, pred: fn(T) -> bool) -> bool |
True when some element satisfies pred. |
count_if(comptime T: type, xs: []T, pred: fn(T) -> bool) -> usize |
How 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) -> A |
Left fold: f(f(f(init, x0), x1), x2). |
find(comptime T: type, xs: []T, pred: fn(T) -> bool) -> ?T |
First element satisfying pred, or null. |
position(comptime T: type, xs: []T, pred: fn(T) -> bool) -> ?usize |
Position 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) -> []T |
The first n elements (or all when shorter). |
drop(comptime T: type, xs: []T, n: usize) -> []T |
Everything 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) -> bool |
True when xs starts with prefix. |
std.log: leveled, structured logging, written in Nexium: a message and named fields, as a line of text a person reads or a line of JSON a machine reads, on stderr. import std.log then: var l = log.Logger.new(log.Level.Info) l.info("listening", [log.int("port", 8080), log.str("host", host)][..]) l.warn("slow answer", [log.float("seconds", 2.5)][..]) l.debug("not shown: below the logger's level", []) var j = log.Logger.json(log.Level.Debug) // one JSON object a line let level = log.parse_level(env.get_or("LOG", "info")) orelse log.Level.Info var request = l.with([log.str("id", id)][..]) // every line carries the id A text line is 2026-09-25T18:04:05.120Z INFO listening port=8080 host=0.0.0.0, a value with a space, = or a quote in quotes; a JSON line is {"time":"2026-09-25T18:04:05.120Z","level":"info","msg":"listening", "port":8080,"host":"0.0.0.0"}.
Types: Level, Field, Logger
| function | what it does |
|---|---|
parse_level(text: []u8) -> ?Level |
The level named by debug, info, warn (or warning) or error, in any case; null for anything else. |
str(key: []u8, value: []u8) -> Field |
A text field. |
int(key: []u8, value: i64) -> Field |
A whole-number field. |
float(key: []u8, value: f64) -> Field |
A floating-point field; a value JSON cannot hold (NaN, the infinities) is written as text. |
flag(key: []u8, value: bool) -> Field |
A true-or-false field. |
(method) new(level: Level) -> Logger |
Text lines on stderr, from level up. |
(method) json(level: Level) -> Logger |
JSON lines on stderr, from level up. |
(method) keeping(level: Level, json: bool) -> Logger |
A logger that keeps its lines in kept rather than writing them: for tests of what a program logs. |
(method) with(self: *Self, fields: []Field) -> Logger |
A logger like this one whose lines carry fields too. |
(method) enabled(self: *Self, level: Level) -> bool |
Does this logger write lines of level? |
(method) log(self: *mut Self, level: Level, msg: []u8, fields: []Field) |
A line at level, if the logger writes that level. |
(method) debug(self: *mut Self, msg: []u8, fields: []Field) |
|
(method) info(self: *mut Self, msg: []u8, fields: []Field) |
|
(method) warn(self: *mut Self, msg: []u8, fields: []Field) |
|
(method) err(self: *mut Self, msg: []u8, fields: []Field) |
A line at Level.Error (error itself is a keyword). |
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
| function | what it does |
|---|---|
parse_addr(s: []u8) -> ?Addr |
Split host:port or [v6]:port; null when there is no valid port. |
port_of(s: []u8) -> ?u16 |
The 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) -> !TcpStream |
Connect with a 10 second timeout. |
(method) connect_timeout(host: []u8, port: u16, timeout_ms: i64) -> !TcpStream |
Connect; timeout_ms 0 waits as long as the OS does. |
(method) from_socket(sock: i64) -> TcpStream |
Wrap 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) -> !String |
Up to n bytes; empty when the peer has closed. |
(method) recv_all(self: *Self) -> !String |
Everything until the peer closes. |
(method) peer(self: *Self) -> !String |
The remote address as ip:port. |
(method) local(self: *Self) -> !String |
The local address as ip:port. |
(method) reader(self: *Self) -> stream.Reader |
A buffered reader over the socket (lines, chunks), with its receive timeout; it does not own the socket. |
(method) writer(self: *Self) -> stream.Writer |
A buffered writer over the socket; flush it before waiting for a reply. |
(method) close(self: *mut Self) |
|
(method) bind(host: []u8, port: u16) -> !TcpListener |
Bind and listen; port 0 picks a free port (see local). |
(method) local(self: *Self) -> !String |
The bound address as ip:port. |
(method) port(self: *Self) -> !u16 |
The bound port. |
(method) accept(self: *Self) -> !TcpStream |
Wait for a connection. |
(method) accept_timeout(self: *Self, timeout_ms: i64) -> !TcpStream |
Wait up to timeout_ms for a connection (error.Timeout otherwise). |
(method) close(self: *mut Self) |
|
(method) bind(host: []u8, port: u16) -> !UdpSocket |
Bind; 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) -> !Datagram |
One datagram of at most n bytes. |
(method) close(self: *mut Self) |
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.
| function | what it does |
|---|---|
gcd(a: u64, b: u64) -> u64 |
Greatest common divisor (Euclid); gcd(0, 0) is 0. |
lcm(a: u64, b: u64) -> u64 |
Least common multiple; lcm(0, n) is 0. |
clamp(x: i64, lo: i64, hi: i64) -> i64 |
x limited to lo..=hi. |
abs_diff(a: i64, b: i64) -> u64 |
|
pow(base: u64, exp: u32) -> ?u64 |
base to the exp, by squaring; null on overflow. |
isqrt(n: u64) -> u64 |
Integer square root: the largest r with r * r <= n. |
is_prime(n: u64) -> bool |
Trial 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) -> u64 |
Sum of the decimal digits. |
to_base(n: u64, base: u64) -> String |
n in base 2..36, upper-case digits. |
from_base(text: []u8, base: u64) -> ?u64 |
Parse in base 2..36 (either case); null on an invalid digit or overflow. |
round_up(n: u64, m: u64) -> u64 |
Round up to a multiple of m (m > 0). |
is_power_of_two(n: u64) -> bool |
True for 1, 2, 4, 8, ... |
next_power_of_two(n: u64) -> u64 |
The smallest power of two >= n (n <= 2^63). |
popcount(n: u64) -> u32 |
Number of set bits. |
std.path: paths as text, written in Nexium: joining, splitting, comparing and normalizing them, without touching the file system (std.fs does that). import std.path then: let cfg = path.join(path.parent(argv0), "app.toml") let ext = path.extension("notes.tar.gz") // "gz" let n = path.normalize("a/./b/../c") // "a/c" let r = path.relative("src/app", "src/lib/x.nx") // "../lib/x.nx" for part in path.components("/usr/local/bin") { } // "/", "usr", "local", "bin" Paths are byte strings. / and \ both separate components on every platform, and a result uses / unless its input used \. A path is absolute when it starts at a root: /, \, a drive (C:\, C:/) or a share (\\server). std.fs keeps its path functions, which call these.
| function | what it does |
|---|---|
root(p: []u8) -> []u8 |
The root a path starts with (/, C:\, \\), or `` for a relative path. |
is_absolute(p: []u8) -> bool |
Does the path start at a root (/x, C:\x, C:/x, \\server)? |
is_relative(p: []u8) -> bool |
Is the path relative to some directory: not is_absolute? |
components(p: []u8) -> List([]u8) |
The root, if any, then each component, separators left out: /usr/local/ -> /, usr, local; a/./b -> a, ., b. |
join(dir: []u8, name: []u8) -> String |
dir/name; a separator is added only when needed, and an absolute name replaces dir. |
join_all(parts: [][]u8) -> String |
Every part joined in turn, as join joins two: join_all(parts[..]). |
parent(p: []u8) -> []u8 |
Everything before the last separator: a/b/c.txt -> a/b, c.txt -> ``, /c.txt -> `/`. |
base_name(p: []u8) -> []u8 |
The last component: a/b/c.txt -> c.txt. |
extension(p: []u8) -> []u8 |
The extension without the dot: a/b.tar.gz -> gz, Makefile -> ``. |
stem(p: []u8) -> []u8 |
The base name without its extension: a/b.tar.gz -> b.tar. |
with_extension(p: []u8, ext: []u8) -> String |
The path with its extension replaced (or added): a/b.txt, md -> a/b.md. |
normalize(p: []u8) -> String |
Collapse . and .. components and repeated separators: a/./b/../c//d -> a/c/d. A leading .. is kept; .. at a root is dropped; an empty result is .. |
strip_prefix(p: []u8, prefix: []u8) -> ?[]u8 |
What follows prefix in p, compared component by component (so a/bc does not start with a/b): a/b/c/d, a/b -> c/d; null when p does not start with prefix. Neither is normalized first. |
starts_with(p: []u8, prefix: []u8) -> bool |
Does p start with prefix, component by component? |
relative(from: []u8, to: []u8) -> ?String |
The path that leads from the directory from to to, both normalized: src/app, src/lib/x.nx -> ../lib/x.nx, and . for the same place. Null when no such path can be written: one is absolute and the other not, their roots differ, or from climbs out through a ... |
to_slash(p: []u8) -> String |
Every \ as /. |
to_native(p: []u8) -> String |
The separators the platform spells paths with: \ on Windows, / elsewhere. |
std.process: run programs and capture what they print, or talk to them while they run, 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. The input is written as the child takes it while its output is read as it comes, so a program that writes before it reads its input, however much, finishes as it would in a terminal. A program can also run alongside, talked to while it runs: var py = try process.start(["python", "-i", "-q"]) try py.stdin.write_line("print(6 * 7)") let answer = try py.stdout.read_line() // "42" let status = try py.wait() // closes its input first var build = try process.start_with(["make"], process.Start{ .stderr = process.Stdio.Merge }) while true { let line = (try build.stdout.read_line()) orelse break println("{}", .{line}) } Output a program writes while this one waits for something else (its input to go in, the other stream, its end) is kept until read, so it never stalls on a full pipe. set_timeout bounds every wait (error.Timeout); kill and terminate end it early. Exit codes and signals have names (EXIT_USAGE, SIGTERM, exit_name, signal_name), and trap_signals lets this program catch Ctrl-C and SIGTERM to finish cleanly (caught, wait_signal).
Types: Output, Options, Status, Stdio, Start, PipeReader, PipeWriter, Child
| function | what it does |
|---|---|
(method) ok(self: *Self) -> bool |
|
(method) text(self: *Self) -> []u8 |
stdout without a trailing newline. |
run_with(argv: [][]u8, opts: Options) -> !Output |
|
run(argv: [][]u8) -> !Output |
Run and capture, inheriting the working directory, with no stdin. |
shell(command: []u8) -> !Output |
Run a command line through the platform shell. |
exit_name(code: i32) -> []u8 |
What an exit code says by convention (sysexits and the shells): "usage" for 64, "not found" for 127; empty for a code with no common meaning. |
signal_name(sig: i32) -> []u8 |
"SIGTERM" for 15; empty for a number without a portable name. |
(method) ok(self: *Self) -> bool |
|
(method) text(self: *Self) -> String |
exit 0 (success), exit 3, signal 15 (SIGTERM). |
(method) read_line(self: *mut Self) -> !?String |
The next line without its \n (or \r\n), waiting for it; null at the end of the output. After a timeout, what came of the line so far is kept for the next call. |
(method) read(self: *mut Self, n: usize) -> !String |
What the program has written and was not read yet, up to n bytes, waiting for some; empty at the end of the output. |
(method) read_all(self: *mut Self) -> !String |
Everything to the end of the output: until the program closes it, usually by ending. |
(method) write(self: *mut Self, data: []u8) -> !void |
Write all of data as the program takes it. error.IoError once it no longer reads (it closed its input or ended). |
(method) write_line(self: *mut Self, line: []u8) -> !void |
line and a \n, in one write. |
(method) close(self: *mut Self) |
End the input: the program reads to its end. |
(method) pid(self: *Self) -> i64 |
The operating system's number for it. |
(method) set_timeout(self: *mut Self, ms: i64) |
Every read and write waits at most ms, then fails with error.Timeout; 0 waits for ever (the default). |
(method) wait(self: *mut Self) -> !Status |
Close its input and wait for it to end. What it writes meanwhile is kept for the readers. |
(method) wait_for(self: *mut Self, ms: i64) -> !?Status |
How it ended, waiting at most ms for it (0: for ever); null when it still runs then. Its input stays open. |
(method) try_wait(self: *mut Self) -> !?Status |
How it ended, without waiting; null while it runs. |
(method) signal(self: *mut Self, sig: i32) -> !void |
Send it a signal (SIGTERM, SIGINT and the others). On Windows, which has none, every signal ends it at once, with exit code 128 + the signal, and wait reports the signal. A program that already ended is left alone. |
(method) terminate(self: *mut Self) -> !void |
Ask it to end: SIGTERM. |
(method) kill(self: *mut Self) -> !void |
End it at once: SIGKILL. |
(method) finish(self: *mut Self) -> !Output |
Close its input, read all it writes and wait for it: what run gives, for a program already talked to. |
(method) close(self: *mut Self) |
Let it go: its pipes close and what was not read is dropped. A program still running goes on by itself. |
start(argv: [][]u8) -> !Child |
Start a program alongside this one, with its input and output as pipes to this one and its errors where this program's go. error.NotFound when there is no such program. |
start_with(argv: [][]u8, how: Start) -> !Child |
Start a program as how says. |
trap_signals() |
Keep SIGINT (Ctrl-C), SIGTERM and SIGHUP from ending this program: each is queued instead, for caught and wait_signal, so the program can finish what it was doing. On Windows: Ctrl-C (SIGINT), Ctrl-Break (SIGBREAK), the console closing (SIGHUP) and logoff or shutdown (SIGTERM); after those three the system ends the program within seconds. |
caught() -> i32 |
The next signal caught and not taken yet, or 0; never waits. |
wait_signal(ms: i64) -> i32 |
Wait for a caught signal; 0 when ms pass first (0: waits for ever). |
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
| function | what it does |
|---|---|
compile(pattern: []u8) -> !Regex |
Compile a pattern; error.InvalidInput when it is malformed. |
is_match(pattern: []u8, text: []u8) -> !bool |
Does pattern match anywhere in text? (Compiles every call.) |
(method) text(self: *Self) -> []u8 |
The matched bytes. |
(method) group(self: *Self, i: usize) -> ?[]u8 |
Group i (0 is the whole match); null when the group did not participate. |
(method) group_count(self: *Self) -> usize |
The number of groups, counting group 0. |
(method) find_at(self: *Self, text: []u8, from: usize) -> ?Match |
The first match at or after byte from. |
(method) find(self: *Self, text: []u8) -> ?Match |
The first match in text. |
(method) is_match(self: *Self, text: []u8) -> bool |
Does 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) -> String |
Replace every match. In repl, $0..$9 insert groups and $$ is a dollar sign. |
(method) split(self: *Self, text: []u8) -> List([]u8) |
The pieces of text between matches. |
std.set: a set of values, written in Nexium over Map(T, bool): its elements are the types a Map takes as keys (integers, bool, char, []u8, String). import std.set then: var seen = set.of(i64) if seen.add(id) { println("new: {}", .{id}) } let common = set.intersection(i64, &a, &b) add, contains and remove take a value like a Map's key: a copy, or for a String set an owned string (name.clone() keeps yours).
Types: Set(T){
| function | what it does |
|---|---|
of(comptime T: type) -> Set(T) |
An empty set. |
(method) add(self: *mut Self, own x: T) -> bool |
Adds x; true when it was not already there. |
(method) contains(self: *Self, x: T) -> bool |
|
(method) remove(self: *mut Self, x: T) -> bool |
Takes x out; true when it was there. |
(method) len(self: *Self) -> usize |
|
(method) is_empty(self: *Self) -> bool |
|
(method) clear(self: *mut Self) |
|
(method) items(self: *Self) -> List(T) |
The elements, in the order each was first added. |
union(comptime T: type, a: *Set(T), b: *Set(T)) -> Set(T) |
The values in a or b. |
intersection(comptime T: type, a: *Set(T), b: *Set(T)) -> Set(T) |
The values in both a and b. |
difference(comptime T: type, a: *Set(T), b: *Set(T)) -> Set(T) |
The values in a that are not in b. |
is_subset(comptime T: type, a: *Set(T), b: *Set(T)) -> bool |
Whether every value of a is in b. |
std.sort: sorting by a comparison of your own, stable sorting, and searching sorted slices, written in Nexium over the slice's swap. import std.sort then: sort.by(Point, points[..], |a: *Point, b: *Point| -> bool { return a.x < b.x }) sort.stable_by(Task, tasks[..], by_priority) // equal elements keep their order sort.by_key(Point, i64, points[..], |p: *Point| -> i64 { return p.y }) let at = sort.binary_search(i64, xs[..], 42) // a position of 42, or null by is a heapsort: in place, O(n log n) comparisons whatever the input, and not stable. The stable sorts merge-sort the positions and then move each element into place along the cycles of the permutation, so no element is ever copied and any element type sorts. xs.sort() sorts by < without a comparison.
| function | what it does |
|---|---|
by(comptime T: type, xs: []mut T, less: fn(*T, *T) -> bool) |
Sorts xs so that less(b, a) holds for no a before b: ascending by less. The order of equal elements is not kept. |
stable_by(comptime T: type, xs: []mut T, less: fn(*T, *T) -> bool) |
Sorts xs ascending by less, keeping equal elements in the order they had. |
by_key(comptime T: type, comptime K: type where K: Ord, xs: []mut T, key: fn(*T) -> K) |
Sorts xs ascending by key of each element, keeping elements with equal keys in the order they had; key is called once per element. |
is_sorted(comptime T: type where T: Ord, xs: []T) -> bool |
Whether xs is ascending by <. |
is_sorted_by(comptime T: type, xs: []T, less: fn(*T, *T) -> bool) -> bool |
Whether xs is ascending by less. |
lower_bound(comptime T: type where T: Ord, xs: []T, x: T) -> usize |
In an ascending xs, the first position whose element is not less than x: where x would go before any equal to it. |
upper_bound(comptime T: type where T: Ord, xs: []T, x: T) -> usize |
In an ascending xs, the first position whose element is greater than x: where x would go after any equal to it. |
binary_search(comptime T: type where T: Ord, xs: []T, x: T) -> ?usize |
In an ascending xs, a position holding x, or null. |
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
| function | what it does |
|---|---|
(method) open(path: []u8) -> !Reader |
Open a file for reading. |
(method) stdin() -> Reader |
Standard input. |
(method) from_handle(handle: i64) -> Reader |
Wrap a handle from io.open; close will not close it. |
(method) from_socket(sock: i64) -> Reader |
Wrap a connected socket from net.connect or net.accept; close will not close it. |
(method) from_socket_timeout(sock: i64, timeout_ms: i64) -> Reader |
from_socket, each read waiting at most timeout_ms for data (error.Timeout then; 0 waits forever). |
(method) read_line(self: *mut Self) -> !?String |
The next line without its \n (or \r\n); null at end of input. |
(method) read_line_max(self: *mut Self, max: usize) -> !?String |
read_line, refusing a line longer than max bytes with error.TooLarge rather than buffering it (0: no limit). Input from a peer that need not end its lines calls for one. |
(method) read(self: *mut Self, n: usize) -> !String |
Up to n bytes; empty at end of input. |
(method) read_all(self: *mut Self) -> !String |
Everything that is left. |
(method) close(self: *mut Self) |
Release the file (the standard streams stay open). |
(method) open(path: []u8) -> !Writer |
Create or replace a file. |
(method) append(path: []u8) -> !Writer |
Open a file for appending. |
(method) stdout() -> Writer |
|
(method) stderr() -> Writer |
|
(method) from_handle(handle: i64) -> Writer |
Wrap a handle from io.open; close will not close it. |
(method) from_socket(sock: i64) -> Writer |
Wrap 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) -> !void |
Hand buffered output to the handle. |
(method) close(self: *mut Self) -> !void |
Flush, then release the file (the standard streams stay open). |
copy(r: *mut Reader, w: *mut Writer) -> !usize |
Copy everything from a reader to a writer; the number of bytes moved. |
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.
| function | what it does |
|---|---|
join(parts: [][]u8, sep: []u8) -> String |
Concatenate parts with sep between them. |
repeat(s: []u8, n: usize) -> String |
s repeated n times. |
pad_left(s: []u8, width: usize, fill: u8) -> String |
Left-pad with fill to at least width bytes. |
pad_right(s: []u8, width: usize, fill: u8) -> String |
Right-pad with fill to at least width bytes. |
center(s: []u8, width: usize, fill: u8) -> String |
Center in width bytes, extra fill on the right. |
count(s: []u8, needle: []u8) -> usize |
How many non-overlapping times needle occurs in s. |
replace(s: []u8, from: []u8, to: []u8) -> String |
Every occurrence of from replaced by to. |
index_from(s: []u8, needle: []u8, start: usize) -> ?usize |
Position of needle at or after start. |
last_index(s: []u8, needle: []u8) -> ?usize |
Position of the last occurrence of needle. |
strip_prefix(s: []u8, prefix: []u8) -> ?[]u8 |
s without a leading prefix, or null when it does not start with it. |
strip_suffix(s: []u8, suffix: []u8) -> ?[]u8 |
s without a trailing suffix, or null when it does not end with it. |
trim_left(s: []u8) -> []u8 |
Leading ASCII whitespace removed. |
trim_right(s: []u8) -> []u8 |
Trailing ASCII whitespace removed. |
is_blank(s: []u8) -> bool |
True 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) -> String |
ASCII letters upper-cased; other bytes unchanged. |
to_lower(s: []u8) -> String |
ASCII letters lower-cased; other bytes unchanged. |
capitalize(s: []u8) -> String |
First ASCII letter upper-cased. |
reverse(s: []u8) -> String |
Bytes 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) -> String |
Truncate to max bytes, appending ... when something was cut. |
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 testing.expect_snapshot("report", rendered) // compares to snapshots/report.txt try testing.snapshot("report", rendered) // the same, as an error union fn lists(r: *mut testing.Rng) -> List(i64) { ... r.size(20) ... r.int(-50, 50) ... } testing.check(List(i64), lists, |xs: *List(i64)| -> bool { ... }) // a property 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 and how to accept the new output. Set NX_UPDATE_SNAPSHOTS=1 to rewrite them all. A property is checked on 100 random values (NX_CASES for more, NX_SEED for another seed). Generators draw from a Rng, which keeps every choice, so a failing case is shrunk by making it again from fewer and lower choices while it still fails: the report is about the smallest case found, with no shrinking code of the generator's own. search is the driver without the panic; the compiler's fuzzer runs on it.
Types: Rng, Search, Failure
| function | what it does |
|---|---|
approx(a: f64, b: f64, eps: f64) -> bool |
Are 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) -> bool |
Did 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) -> !void |
Compare actual to <dir>/<name>.txt; write it when missing or when NX_UPDATE_SNAPSHOTS is set. |
snapshot(name: []u8, actual: []u8) -> !void |
snapshot_in("snapshots", name, actual). |
expect_snapshot_in(dir: []u8, name: []u8, actual: []u8) |
snapshot_in, in the expect_ form: a mismatch fails the test naming the file, the first differing line and how to accept the new output; a file that cannot be read or written fails it too, instead of returning an error for the test to handle. |
expect_snapshot(name: []u8, actual: []u8) |
expect_snapshot_in("snapshots", name, actual). |
rng(seed: u64) -> Rng |
|
(method) next(self: *mut Self) -> u64 |
The next choice: 64 random bits (splitmix64), or the kept one when replaying (0 past the end of them). Shrinks toward 0. |
(method) below(self: *mut Self, n: u64) -> u64 |
A number below n (0 when n is 0); shrinks toward 0. |
(method) pick(self: *mut Self, n: usize) -> usize |
An index into n things; shrinks toward the first. |
(method) flip(self: *mut Self) -> bool |
True or false alike; shrinks toward false. |
(method) int(self: *mut Self, lo: i64, hi: i64) -> i64 |
An integer from lo to hi, both included; shrinks toward 0, or toward the end nearer to it when the range does not hold 0. |
(method) float(self: *mut Self) -> f64 |
A float from 0 up to 1, 1 left out; shrinks toward 0. |
(method) size(self: *mut Self, max: usize) -> usize |
A length up to max, short ones likelier; shrinks toward 0. |
(method) bytes(self: *mut Self, max: usize) -> String |
Bytes of any value, up to max of them. |
(method) ascii(self: *mut Self, max: usize) -> String |
Printable ASCII, up to max bytes; shrinks toward as. |
(method) text(self: *mut Self, max: usize) -> String |
UTF-8 text of up to max characters: ASCII mostly, with accented Latin, Greek, Cyrillic, CJK, emoji and combining marks among it. |
replay(comptime T: type, choices: []u64, gen: fn(*mut Rng) -> T) -> T |
The value gen makes from kept choices. |
search(comptime T: type, how: Search, gen: fn(*mut Rng) -> T, holds: fn(*T) -> bool) -> ?Failure |
Look for a case where holds is false among the values gen makes, and shrink the first found; null when every case held. holds answers with false; a panic in it ends the test as it stands, unshrunk. |
seed() -> u64 |
The seed check starts from: NX_SEED when it is set, else 1, so a run repeats. |
check(comptime T: type, gen: fn(*mut Rng) -> T, holds: fn(*T) -> bool) |
Check that holds is true of every value gen makes: 100 of them (NX_CASES sets how many, NX_SEED the seed). A failure is shrunk to the smallest failing case found and fails the test with the seed that repeats it; check_show prints the value too. |
check_show(comptime T: type, gen: fn(*mut Rng) -> T, holds: fn(*T) -> bool, show: fn(*T) -> String) |
check, with the smallest failing value written by show in the failure. |
std.text: UTF-8 text by code point and by grapheme cluster, written in Nexium, with the Unicode tables it needs. import std.text then: let n = text.char_count("héllo") // 5 code points, not 6 bytes for c in text.scalars("héllo") { ... } // chars (Unicode scalars) let g = text.grapheme_count("🇯🇵 e\u{301}") // 3: a flag, a space, an é for g in text.graphemes(s) { ... } // what a reader counts as characters let w = text.width("日本語") // 6 columns on a terminal let cell = text.pad_right(text.truncate_width(name, 20), 20) let s = text.to_upper("straße") // "STRASSE" let same = text.eq_ignore_case("Straße", "STRASSE") let t = text.truncate("héllo wörld", 5) // "héllo", never mid-character Strings are bytes; this module reads them as UTF-8, tolerating bad input (an invalid byte decodes as U+FFFD and advances one byte). Grapheme clusters are Unicode's extended grapheme clusters (UAX #29): a letter and its marks, a Hangul syllable, an emoji sequence joined by ZWJ, a flag, an Indic conjunct. Case mapping is Unicode's full mapping for every script, without the rules that depend on a language (Turkish and Lithuanian i); to_lower writes a final sigma where a word ends. width counts terminal columns as terminals draw them: East Asian wide and fullwidth characters and emoji sequences take two, marks and zero-width characters none. The tables at the end are generated by scripts/unicode_tables.py (UNICODE_VERSION says from which version).
Types: Decoded
| function | what it does |
|---|---|
decode_at(s: []u8, i: usize) -> Decoded |
Decode the code point starting at byte i. Invalid input yields U+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) -> String |
A code point as a String. |
is_valid(s: []u8) -> bool |
Is the text well-formed UTF-8? |
chars(s: []u8) -> List(u32) |
All code points. |
char_count(s: []u8) -> usize |
The number of code points. |
byte_offset(s: []u8, n: usize) -> usize |
The byte offset of the nth code point (or s.len when past the end). |
char_at(s: []u8, n: usize) -> ?u32 |
The nth code point, or null. |
slice(s: []u8, from: usize, to: usize) -> []u8 |
Code points from (inclusive) to to (exclusive), as a slice of s. |
truncate(s: []u8, n: usize) -> []u8 |
The first n code points; never cuts a character in half. |
reverse(s: []u8) -> String |
The code points in reverse order. |
scalars(s: []u8) -> List(char) |
The Unicode scalars, as chars (an invalid byte reads as U+FFFD). |
grapheme_end(s: []u8, i: usize) -> usize |
The byte offset where the grapheme cluster that starts at byte i ends. A grapheme cluster is what a reader takes for one character: a letter and its marks, a Hangul syllable, an emoji sequence joined by ZWJ, a flag, an Indic conjunct, CR LF (Unicode's extended grapheme clusters, UAX #29). |
graphemes(s: []u8) -> List([]u8) |
The grapheme clusters, as slices of the text. |
grapheme_count(s: []u8) -> usize |
The number of grapheme clusters: the characters a reader counts. |
truncate_graphemes(s: []u8, n: usize) -> []u8 |
The first n grapheme clusters; never splits one. |
is_zero_width(cp: u32) -> bool |
Does the code point take no columns (combining and enclosing marks, format and zero-width characters, controls, Hangul vowels and finals)? |
is_wide(cp: u32) -> bool |
Does the code point take two columns (East Asian wide and fullwidth, emoji among them)? |
char_width(cp: u32) -> usize |
Columns a code point takes on a terminal: 0, 1 or 2. |
width(s: []u8) -> usize |
Columns the text takes on a terminal, grapheme cluster by cluster, as terminals draw it: e with an accent is one, a family emoji two. |
pad_right(s: []u8, columns: usize) -> String |
Pad on the right to columns terminal columns (by width, not bytes). |
pad_left(s: []u8, columns: usize) -> String |
Pad on the left to columns terminal columns: the text right-aligned. |
truncate_width(s: []u8, columns: usize) -> []u8 |
The longest start of the text that fits in columns terminal columns, cut between grapheme clusters. |
upper_char(cp: u32) -> u32 |
Upper-case a code point by Unicode's simple mapping, one code point to one: ß stays ß here, where to_upper writes SS. |
lower_char(cp: u32) -> u32 |
Lower-case a code point by Unicode's simple mapping. |
fold_char(cp: u32) -> u32 |
Case-fold a code point by Unicode's simple folding (ς and σ fold alike); fold does the full folding of a text. |
to_upper(s: []u8) -> String |
Upper-case by Unicode's full mapping, every script: straße becomes STRASSE, fi becomes FI. |
to_lower(s: []u8) -> String |
Lower-case by Unicode's full mapping, with ς for a sigma that ends a word: ΟΔΟΣ becomes οδος. |
fold(s: []u8) -> String |
Case-fold by Unicode's full folding, to compare or look up text without regard to case: Straße, STRASSE and strasse fold alike. |
eq_ignore_case(a: []u8, b: []u8) -> bool |
Compare ignoring case, by full case folding. |
std.thread: threads, channels, select, mutexes and atomics, 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() var hits = thread.atomic(0) // an i64 changed without a lock _ = hits.add(1) // two channels at once: which has a value (or closed), null after 1 s let which = thread.select2(Job, bool, &mut jobs, &mut quit, 1000) orelse continue // threads that end before the call does, so they may point into locals thread.each(Stage, stages[..], run_stage) // a thread for every item thread.both(Producer, Consumer, &mut p, produce, &mut c, consume) A thread function takes a pointer to its argument, which the Thread owns until join returns the result. Channels, mutexes and atomics are values that threads share by pointer; the owner must join every thread using them before letting them go out of scope (each and both do it themselves), and call free when done. Panics inside a thread surface from join. Timeouts are in milliseconds, and 0 waits for ever.
Types: Task(T,, Thread(T,, WorkerTask(T){, Worker(T){, Channel(T){, Mutex(T){, Atomic
| function | what 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) -> R |
Wait for the thread and take its result. Joining twice panics. |
(method) arg(self: *Self) -> *T |
The 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) -> *T |
The argument after the thread finished (for results written in place). |
count() -> usize |
The number of hardware threads. |
channel(comptime T: type) -> Channel(T) |
|
(method) send(self: *mut Self, own value: T) |
|
(method) recv(self: *mut Self) -> ?T |
The next value, waiting for one; null when closed and empty. |
(method) recv_for(self: *mut Self, ms: i64) -> !?T |
The next value, waiting at most ms for one (0: for ever): error.Timeout when none came in time, null when the channel is closed and empty. |
(method) try_recv(self: *mut Self) -> ?T |
The 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) is_closed(self: *mut Self) -> bool |
|
(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 T |
Take 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. |
select2(comptime A: type, comptime B: type, a: *mut Channel(A), b: *mut Channel(B), ms: i64) -> ?usize |
Wait until one of two channels has a value or is closed: 0 for a, 1 for b (a first when both are), or null when ms pass first (0: waits for ever). Then take the value with try_recv: another receiver may have taken it first, and a closed channel stays ready, so leave one out once it is closed and drained. |
select(comptime T: type, chans: []*mut Channel(T), ms: i64) -> ?usize |
select2 over any number of channels of one type: the index of the first with a value or closed, or null when ms pass first (0: waits for ever). |
each(comptime T: type, items: []mut T, f: fn(*mut T) -> void) |
Run f on every item, each on a thread of its own, all at once, and return when every one has finished. Nothing started here outlives the call, so the items may point into the caller's locals, and the threads may wait on each other (the stages of a pipeline over channels). A panic in one is raised here once all have ended. To split work over the cores, for parallel is the tool. |
both(comptime A: type, comptime B: type, a: *mut A, fa: fn(*mut A) -> void, b: *mut B, fb: fn(*mut B) -> void) |
Run fa(a) and fb(b) on two threads at once and return when both have finished, as each does. |
atomic(value: i64) -> Atomic |
|
(method) load(self: *Self) -> i64 |
|
(method) store(self: *mut Self, value: i64) |
|
(method) add(self: *mut Self, n: i64) -> i64 |
Add n; the value before. |
(method) sub(self: *mut Self, n: i64) -> i64 |
Subtract n; the value before. |
(method) swap(self: *mut Self, value: i64) -> i64 |
Put value in; the value before. |
(method) compare_swap(self: *mut Self, expected: i64, new: i64) -> bool |
Put new in if the value is expected; whether it was. |
std.time: dates, durations, time zones 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 ny = try time.zone("America/New_York") // from the platform's database println("{}", .{ny.format(time.now(), "%H:%M %Z")}) // 00:15 EDT let d = time.Duration.minutes(90) println("{} {}", .{d.text(), d.iso()}) // 1h 30m PT1H30M 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. Time zones come from the platform's database: the zoneinfo files on Linux, macOS and the BSDs (under /usr/share/zoneinfo, or $TZDIR), ICU on Windows (10, version 1903 and later). now_local and local take only the local offset, from the C library; local_zone is the machine's zone with its history.
Types: DateTime, Duration, Period, RuleDay, Rule, Zone, Stopwatch
| function | what it does |
|---|---|
is_leap(year: i32) -> bool |
|
days_in_month(year: i32, month: u8) -> u8 |
|
weeks_in_year(year: i32) -> u8 |
52 or 53: a year has 53 ISO weeks when it begins on a Thursday, or on a Wednesday in a leap year. |
utc(ms: i64) -> DateTime |
Break an instant down in UTC. |
local(ms: i64) -> DateTime |
Break an instant down in the machine's local time zone. |
with_offset(ms: i64, offset_min: i32) -> DateTime |
Break an instant down at a fixed offset in minutes east of UTC. |
now_utc() -> DateTime |
The current instant, in UTC. |
now_local() -> DateTime |
The current instant, in local time. |
date(year: i32, month: u8, day: u8) -> ?DateTime |
A date at midnight UTC; null when the fields do not name a real day. |
from_week(year: i32, week: i64, day: i64) -> ?DateTime |
The date of an ISO week date (day 1 is Monday); null for a week the year does not have. |
from_ordinal(year: i32, n: i64) -> ?DateTime |
The date of an ordinal date, the nth day of the year (1-based); null past the year's last day. |
(method) to_ms(self: *Self) -> i64 |
Milliseconds since the epoch (the offset is subtracted back out). |
(method) to_utc(self: *Self) -> DateTime |
The same instant expressed in UTC. |
(method) weekday(self: *Self) -> u8 |
Day of the week, 0 = Monday ... 6 = Sunday. |
(method) day_of_year(self: *Self) -> u16 |
Day of the year, 1-based. |
(method) iso_week(self: *Self) -> (i32, u8) |
The ISO 8601 week: the week-numbering year and the week, 1 to 53. Weeks begin on Monday and week 1 holds the year's first Thursday, so January 1 can be in the last week of the year before. |
(method) date_text(self: *Self) -> String |
YYYY-MM-DD. |
(method) week_date(self: *Self) -> String |
The ISO 8601 week date, 2026-W38-6 (the day is 1 for Monday). |
(method) ordinal_date(self: *Self) -> String |
The ISO 8601 ordinal date, 2026-262. |
(method) time_text(self: *Self) -> String |
HH:MM:SS. |
(method) offset_text(self: *Self) -> String |
The offset as Z, or +HH:MM / -HH:MM. |
(method) iso(self: *Self) -> String |
ISO 8601 / RFC 3339: 2026-09-19T04:15:14.123Z, ...+02:00. |
(method) iso_basic(self: *Self) -> String |
ISO 8601's basic format, with no separators: 20260919T041514.123Z, ...+0200. |
(method) format(self: *Self, spec: []u8) -> String |
strftime-style formatting: %Y %m %d %H %M %S, %3 (millis), %y (two-digit year), %I %p (12-hour clock, AM/PM), %a %A %b %B (day and month names, short and full), %j (day of year), %G %V %u (ISO week year, week and weekday, 1 for Monday), %s (seconds since the epoch), %z (offset, +02:00), %Z (UTC, or the offset as the zoneinfo files abbreviate it, +02; Zone.format gives the zone's own, CEST) and %%. Unknown letters are copied through. |
(method) plus(self: *Self, d: Duration) -> DateTime |
The instant d later, at the same offset. |
(method) minus(self: *Self, d: Duration) -> DateTime |
The instant d earlier, at the same offset. |
(method) until(self: *Self, other: *DateTime) -> Duration |
The time from this instant to other, negative when other is earlier. |
(method) add_days(self: *Self, n: i64) -> DateTime |
n calendar days later (earlier when negative), at the same clock time and offset. In a zone whose offset changes in between, z.at(z.instant(&later)) keeps the clock time. |
(method) add_months(self: *Self, n: i64) -> DateTime |
n months later; a day the month lacks becomes its last, so January 31 plus a month is February 28 or 29. |
(method) add_years(self: *Self, n: i64) -> DateTime |
n years later; February 29 becomes February 28 in a common year. |
parse_iso(s: []u8) -> ?DateTime |
Parse ISO 8601 / RFC 3339 text. The date is 2026-09-19 or 20260919, a week date 2026-W38-6 or 2026W386, or an ordinal date 2026-262 or 2026262. A time may follow after T (or a space): 04:15:14.123 or 041514.123, to the hour, the minute or the second, with . or , before a fraction of a second (read to the millisecond). An offset may end it: Z, +02:00, +0200 or +02. Missing parts are zero; null when the text 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) -> Duration |
The span from a to b (instants in ms). |
(method) since(ms: i64) -> Duration |
The 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) whole_seconds(self: *Self) -> i64 |
Whole units, rounded toward zero. |
(method) whole_minutes(self: *Self) -> i64 |
|
(method) whole_hours(self: *Self) -> i64 |
|
(method) whole_days(self: *Self) -> i64 |
|
(method) plus(self: *Self, other: Duration) -> Duration |
|
(method) minus(self: *Self, other: Duration) -> Duration |
|
(method) times(self: *Self, n: i64) -> Duration |
n times as long. |
(method) div(self: *Self, n: i64) -> Duration |
An nth of it, rounded toward zero. |
(method) ratio(self: *Self, other: Duration) -> f64 |
How many times other goes into it: hours(3).ratio(minutes(90)) is 2.0. |
(method) neg(self: *Self) -> Duration |
|
(method) abs(self: *Self) -> Duration |
|
(method) is_zero(self: *Self) -> bool |
|
(method) is_negative(self: *Self) -> bool |
|
(method) truncate(self: *Self, unit: Duration) -> Duration |
Cut to a whole number of units, toward zero: millis(1999).truncate(seconds(1)) is one second. |
(method) round(self: *Self, unit: Duration) -> Duration |
The nearest whole number of units; a half goes away from zero. |
(method) text(self: *Self) -> String |
Human text: 250ms, 3.5s, 2m 05s, 1h 02m, 3d 04h. |
(method) iso(self: *Self) -> String |
ISO 8601: PT1H30M, PT0.25S, PT76H, -PT1.5S, PT0S. Hours are the largest unit, since a day in a zone is not always 24 of them. |
(method) parse_iso(s: []u8) -> ?Duration |
Parse ISO 8601 duration text: PT1H30M, PT0.25S, P2DT3H, P1W, -PT5M. A day is 24 hours here and a week 7 days; years and months (P1Y, P2M), whose length depends on the calendar, are null, as is anything that is not a duration. The last number may have a fraction (PT1.5H), read to the millisecond. |
add(ms: i64, d: Duration) -> i64 |
instant + duration. |
utc_zone() -> Zone |
UTC as a zone. |
zone(name: []u8) -> !Zone |
A time zone by name: an IANA name from the platform's database (Europe/Berlin, America/New_York, Asia/Kolkata), UTC, a fixed offset (+05:30, -0800), or a POSIX rule (EST5EDT,M3.2.0,M11.1.0). error.NotFound when the database has no such zone, or there is no database (the playground); error.InvalidInput for text that cannot name a zone, or a database file that is not one. |
local_zone() -> Zone |
The machine's time zone: $TZ when it is set (a zone's name, the path of a TZif file, or a POSIX rule such as EST5EDT,M3.2.0,M11.1.0; set but empty is UTC), else the system's: /etc/localtime on Linux, macOS and the BSDs (named by /etc/timezone where there is one, else Local), the zone Windows is set to through ICU. When none is found, a zone with the C library's offset now, named Local. |
from_tzif(name: []u8, b: []u8) -> !Zone |
A zone from the bytes of a TZif file (RFC 8536), the form the zoneinfo database is kept in; error.InvalidInput when they are not one. |
(method) offset_seconds_at(self: *Self, ms: i64) -> i32 |
Seconds east of UTC at an instant. A few offsets, the local mean time places kept before they took a standard one (mostly before 1900), are not whole minutes. |
(method) offset_at(self: *Self, ms: i64) -> i32 |
Minutes east of UTC at an instant, to the nearest minute: the offset of the DateTime that at gives. |
(method) is_dst_at(self: *Self, ms: i64) -> bool |
Whether daylight saving time is in force at an instant. |
(method) abbrev_at(self: *Self, ms: i64) -> String |
The zone's abbreviation at an instant: CEST, PST, or an offset such as +0530 where the database has no name. |
(method) at(self: *Self, ms: i64) -> DateTime |
An instant broken down in this zone. |
(method) now(self: *Self) -> DateTime |
The current instant in this zone. |
(method) instant(self: *Self, dt: *DateTime) -> i64 |
The instant a wall-clock time names in this zone; the offset in dt is ignored. A time that happens twice, when clocks are turned back, is the first; one that is skipped, when they are turned forward, is read with the offset before the change, so 02:30 on the night clocks jump from 02:00 to 03:00 is 03:30. |
(method) format(self: *Self, ms: i64, spec: []u8) -> String |
strftime-style text of an instant in this zone: DateTime.format's letters, with %Z the zone's abbreviation (CEST). |
parse_rule(s: []u8) -> ?Rule |
Parse a POSIX TZ rule: EST5EDT,M3.2.0,M11.1.0, <+0530>-5:30, AEST-10AEDT,M10.1.0,M4.1.0/3. Its offsets are written west of UTC, the other way round from everywhere else; a rule with a daylight name and no dates follows the United States' dates. null when the text is not a rule. |
(method) start() -> Stopwatch |
|
(method) elapsed_ms(self: *Self) -> f64 |
Elapsed time in milliseconds, fractional. |
(method) elapsed(self: *Self) -> Duration |
Elapsed time as a Duration (whole milliseconds). |
(method) lap(self: *mut Self) -> Duration |
Restart and return what had elapsed. |
std.toml: TOML 1.0 (toml.io), written in Nexium: reading a document into values, finding a value by its key, and writing values back as TOML. import std.toml then: let doc = try toml.parse(text) let name = toml.as_str(toml.lookup(&doc, "package.name") orelse return) orelse "" for dep in toml.keys(toml.get(&doc, "dependencies") orelse return) { } if let why = toml.problem(text) { eprintln("{}", .{why}) } // "line 3: ..." let out = toml.stringify(&doc) Every value TOML has: strings (basic and literal, each also multi-line), 64-bit integers (decimal, hex, octal, binary), floats (with inf and nan), booleans, dates and times (kept as written: offset date-time, local date-time, local date, local time), arrays, tables, inline tables and arrays of tables, under TOML's rules that a key and a table are defined once. A document that breaks one is error.InvalidInput; problem says where and why.
Types: Toml, Entry, StampKind, Stamp
| function | what it does |
|---|---|
parse(text: []u8) -> !Toml |
The document's values: a Table of its keys. |
problem(text: []u8) -> ?String |
Why text is not a TOML document (line N: ...), or null when it is. |
get(v: *Toml, key: []u8) -> ?*Toml |
The value of a table's key, or null. |
lookup(v: *Toml, dotted: []u8) -> ?*Toml |
The value at a dotted path of bare keys, package.name, or null. |
at(v: *Toml, i: usize) -> ?*Toml |
An array's item i, or null. |
len(v: *Toml) -> usize |
How many items an array holds or keys a table has; 0 for anything else. |
is_table(v: *Toml) -> bool |
Is it a table (a document, a [header]'s, or an inline one)? |
is_array(v: *Toml) -> bool |
Is it an array (an array of tables included)? |
keys(v: *Toml) -> List([]u8) |
A table's keys in the document's order; empty for anything else. |
as_str(v: *Toml) -> ?[]u8 |
|
as_int(v: *Toml) -> ?i64 |
|
as_float(v: *Toml) -> ?f64 |
A float's value, or an integer's as a float. |
as_bool(v: *Toml) -> ?bool |
|
as_time(v: *Toml) -> ?Stamp |
A date or time as the document wrote it, and which form it is. |
stringify(v: *Toml) -> String |
A table as a TOML document: its plain keys first, then each table under a [header] and each array of tables under [[headers]]. Anything that is not a table is written as a value. |
std.uuid: UUIDs (RFC 9562), written in Nexium: random ones (version 4), ones that sort by the time they were made (version 7), and their text. import std.uuid then: let id = try uuid.v4() // 122 random bits println("{}", .{id.text()}) // "9f1c2e7a-4b3d-4e8f-a1b2-c3d4e5f60718" let row = try uuid.v7() // the time first: sorts by creation let back = uuid.parse(text) orelse return error.BadId The random bits come from random.secure, the operating system's generator, so an id cannot be guessed from the ones before it; v4 and v7 fail only where the system has no generator (IoError).
Types: Uuid
| function | what it does |
|---|---|
(method) text(self: *Self) -> String |
The canonical text: 32 lowercase hex digits in groups of 8, 4, 4, 4 and 12, joined by hyphens. |
(method) version(self: *Self) -> u8 |
The version: 4 for random, 7 for time-ordered, 0 for the nil UUID. |
(method) is_nil(self: *Self) -> bool |
Is every bit zero? |
(method) time_ms(self: *Self) -> ?i64 |
When a version 7 UUID was made, in milliseconds since the epoch; null for any other version. |
(method) eq(self: *Self, other: *Uuid) -> bool |
Is it the same UUID? |
v4() -> !Uuid |
A random UUID (version 4). |
v7() -> !Uuid |
A UUID that begins with the time it was made (version 7): 48 bits of milliseconds since the epoch, then 74 random bits, so ids made later sort after earlier ones, as text and as bytes, to the millisecond. |
nil() -> Uuid |
The nil UUID, every bit zero. |
max() -> Uuid |
The max UUID, every bit one. |
from_bytes(data: []u8) -> ?Uuid |
A UUID from its 16 bytes; null for any other length. |
parse(text: []u8) -> ?Uuid |
A UUID from its text: the canonical form with hyphens, in either case, or the 32 hex digits alone, either one also in braces or after urn:uuid:. Null for anything else. |
std.websocket: a WebSocket client (RFC 6455), written in Nexium over std.http's transports. import std.websocket then: var ws = try websocket.connect("ws://localhost:8080/chat", 10000) try ws.send_text("hello") while true { let m = try ws.recv() // a whole message if m.op == websocket.CLOSE { break } // the server's close, or the end println("{}", .{m.data}) } ws.close(1000, "done") var gw = try websocket.connect_with(NxTls, &mut layer, "wss://gateway.discord.gg/?v=10", 10000) var api = websocket.socket(NxTls, &mut layer) // with headers of its own api.header("Authorization", token) try api.open("wss://example.com/stream", 10000) wss:// goes over a TLS layer, the slot std.http's client uses (http.Transport, which nxtls fills). No extensions are asked for (no compression). A message arrives whole, its fragments joined, up to max_message bytes; recv answers a ping with a pong on its way. The first protocol mistake by the server ends the connection, with the reason in problem. The handshake's key and every frame's mask come from random.secure. A recv that waits longer than the timeout is error.Timeout and may be called again: nothing that came is lost.
Types: Message, Decoder, Socket(T){
| function | what it does |
|---|---|
accept_for(key: []u8) -> String |
The Sec-WebSocket-Accept a server answers key with: the base64 of the SHA-1 of the key and RFC 6455's GUID. |
request(host: []u8, path: []u8, key: []u8, headers: []http.Header) -> String |
The opening request for host (the Host header, as the server knows itself) and path (with its query); headers are added. |
check_response(head: []u8, key: []u8) -> ?String |
What is wrong with the server's answer to the handshake (head, up to but not including the blank line), or null when it accepts key. |
frame(op: u8, payload: []u8, mask: []u8) -> String |
A client frame: final, opcode op, masked with the 4 bytes of mask. |
close_payload(code: u16, reason: []u8) -> String |
The payload of a close frame: the code, big-endian, then the reason, cut to 123 bytes (a control frame carries 125) at a character's start. |
(method) new(max: usize) -> Decoder |
|
(method) feed(self: *mut Self, data: []u8) |
|
(method) next(self: *mut Self) -> ?Message |
The next whole message, a control frame as it comes (PING, PONG, CLOSE), or null when more bytes are needed or problem is set. |
connect(url: []u8, timeout_ms: i64) -> !Socket(http.Plain) |
Connects to a ws:// URL and completes the handshake; the connect and each wait for data take at most timeout_ms (0: no limit). |
connect_with(comptime T: type where T: http.Transport, tls: *mut T, url: []u8, timeout_ms: i64) -> !Socket(T) |
Connects to a wss:// (or ws://) URL, TLS over tls, a TLS layer: an http.Transport, such as nxtls's. |
socket(comptime T: type, tls: ?*mut T) -> Socket(T) |
A socket to open, after header has added what the handshake should carry (Authorization, Origin, Sec-WebSocket-Protocol). tls may be null for ws://. |
(method) header(self: *mut Self, name: []u8, value: []u8) |
A header for the handshake to carry. |
(method) open(self: *mut Self, url: []u8, timeout_ms: i64) -> !void |
Connects to a ws:// or wss:// URL and completes the handshake; wss:// without a TLS layer is error.Unsupported, a URL of another scheme and an answer that is not a WebSocket's are error.InvalidInput (problem says how). |
(method) set_timeout(self: *mut Self, ms: i64) |
How long each wait for data takes from now on, in ms (0: no limit): recv is error.Timeout past it, and can be called again. |
(method) is_open(self: *Self) -> bool |
Whether messages can still be sent. |
(method) send_text(self: *mut Self, data: []u8) -> !void |
Sends a text message; data should be UTF-8. |
(method) send_binary(self: *mut Self, data: []u8) -> !void |
Sends a binary message. |
(method) ping(self: *mut Self, data: []u8) -> !void |
Sends a ping (at most 125 bytes); its pong comes back through recv. |
(method) recv(self: *mut Self) -> !Message |
The next message: TEXT or BINARY whole, a PONG, or CLOSE, either the server's (its code and reason) or the end of the connection (1006, with problem saying so). A ping is answered on the way. error.Timeout when nothing whole came in time (call again: nothing is lost), error.Closed once a CLOSE was returned, and error.InvalidInput for the server's protocol mistake (problem says which), which ends the connection. |
(method) close(self: *mut Self, code: u16, reason: []u8) |
Closes the connection with a status (1000: normal) and a reason: sends the close frame, reads until the server's close answers it, the connection ends or a wait times out, then ends the connection. Messages that come meanwhile are dropped. |