On this page
Now: 1.5.0, and 1.6 next Phase 1: a language you can write your tools in (0.3) Phase 2: a language that talks to the world (0.4) Phase 3: a language other people can build on (0.5) Phase 4: the compiler in Nexium (0.6 to 1.0) Phase 5: 1.0 Past 1.0 What 1.0 is not yet Before anything: the open bugs, and 1.0.3 First: the quick wins, in order 1.1: the language the compiler wanted 1.2: memory safety without a garbage collector 1.3: the toolchain grown up 1.4: a standard library people stop supplementing 1.5: platforms 1.6: the runtime that release builds deserve 1.7: the seam, both ways Errors you can fix alone Tools only this language can have The clickable line: windows, wizards and the REPL 2.0 candidates: questions the spec review should settle Ecosystem, in parallel with all of the above Always Not plannedNexium roadmap
Where the language is, where it goes, and in what order. Dates are not promises; the order is. Each phase ends when its exit criteria hold, and every item lands with tests, docs, and a decision entry when it settles a design question. This file is updated when a phase closes.
The spec's one sentence is the compass: a language complete enough to build everything in, that is also the best thing to adopt for one piece of something else. The second half is ahead of the first, on purpose.
Now: 1.5.0, and 1.6 next#
The work in progress, dated and with its numbers, is in docs/progress.md; this file is the plan.
What exists and is verified on Windows, Linux, and macOS (1.1 to 1.5 each have a section under Past 1.0 saying what it added):
- The language: structs, records, enums,
ref classwith ARC andweak, distinct types, traits anddyn Traitwith effect bounds, generics by monomorphization, closures with explicit captures, error unions, optionals and?.chaining,defer/errdefer, labeled blocks,ownparameters, branch-aware moves, binary pattern matching and construction, slice patterns andx @ pat, tuple destructuring, iterators,derive(Clone), named format arguments,comptimeandif comptime,for parallel,using arena, recursive types throughList, matching through pointers. - Memory safety without a garbage collector: the view rules of 1.2, which keep a view from outliving what it views, errors since 1.3.
- Effects: inferred for every function, checked against negative bounds,
panicsdischarged by proof, effects deciding the C ABI of exports. - Interop:
@cImportof C headers, vendored C,nx shipto a C library, a Python wheel, a Rust crate, an npm package, a WebAssembly module with a JavaScript loader, an installer. - Standard library in Nexium, embedded in the compiler: twenty-nine modules (
strings,lists,bytes,num,json,args,fs,time,regex,text,testing,stream,net,http,websocket,thread,process,sort,heap,set,deque,hash,path,env,uuid,log,csv,toml,base64), withrandom.secureand HTTPS over the platform's own TLS. - Tools, all written in Nexium:
build run test bench debug check effects explain audit refcounts leaks size layout emit-c tir fmt fix doc ship version doctor upgrade install lsp repl completions man play topo, the packages'init add fetch update, and-eand-pfor one line. The language server answers from the checker, and a large program's debug build compiles a C file per module, only the ones that changed. - Platforms: Windows, Linux and macOS, and Linux and Windows on ARM, at tier 1 (every suite in CI, a download each); 32-bit x86 Windows and Linux and ARMv7 Linux at tier 2 (their programs run in CI, a download each); RISC-V 32 tested under QEMU; WebAssembly programs under Node.js's WASI and libraries in a page (
docs/platforms.md). - Distribution: a Windows installer with bundled Zig, a macOS/Linux install script with checksum verification,
pipandnpmpackages (nexium-lang), Homebrew, Scoop and Chocolatey (winget awaits its first merge),.deband.rpm, Nix and mise, container images, andnx upgrade; editor support for VS Code, Vim, Neovim, Helix, Zed, Emacs, Kate, JetBrains, Sublime Text, Notepad++ and nano, a GitHub Action, and a release template for Nexium programs. - Self-hosting: the compiler is written in Nexium and builds itself from the C it emits (
bootstrap/nx.c); no other compiler is involved. - nexium-gui: an immediate-mode GUI in Nexium on a 200-line C window layer.
- Documentation: the site at londopy.github.io/nexium, built by a Nexium program from the repository's Markdown, and the Topo, a 23-chapter tutorial whose every program the tests run, with 41 exercises graded in the terminal (
nx topo) and in the page, which runs the programs itself.
Numbers, at 1.5.0: 61.8k lines of Nexium (35.6k of them the compiler and its tools), 4.5k of C (the runtime and the GUI window layer), 579 std functions, 32 examples, 21 tutorial programs, 75 spec conformance cases and 101 compile-fail cases, 14 harness suites, verified by CI on five machines (the three desktops, Linux and Windows on ARM), for four more targets (32-bit x86, ARMv7, RISC-V 32, WebAssembly), and under the sanitizers.
Phase 1: a language you can write your tools in (0.3)#
Goal: someone replaces a Python or Rust command-line tool with Nexium and misses nothing for the tool's own needs.
std.fs: list directories, stat, create and remove, paths (join, parent, extension, absolute), temp files. Runtime section for the platform calls.std.time: wall-clock breakdown, formatting, durations, monotonic timers.std.regex: a backtracking-free engine (Thompson or Pike VM) in Nexium, with the usual syntax subset; a stress test of the language by design.std.text: UTF-8 iteration by code point, grapheme-agnostic width, case mapping beyond ASCII where cheap.std.testingconveniences:expect_err, approximate float comparison, snapshot files.io: buffered readers and writers, stdin as a stream, environment variables as a map.- Language:
whilewithelse,matchon tuples of enums, integer ranges inforwith steps,Stringslicing helpers,?Tchaining (a?.b) if the spec's ergonomics survive review. - Tools:
nx fmtgains line joining for trailing commas only;nx doccovers std;nx test --filterand--verbose. - Done ahead of the phase:
nx repl(the interactive session on the interpreter) and a Start menu entry that opens it.
Exit: examples/tool.nx, a real utility (a log grep with regex, dates, and file walking) under 300 lines, runs on all three platforms from the release. Status: done except the nx fmt trailing-comma rule (?T chaining, a?.b, landed in 1.1). std.fs, std.time, std.regex, std.text, std.testing, std.stream, range steps, while/else, full match exhaustiveness, nx test --filter/--verbose, os.environ, and examples/tool.nx (142 lines) shipped in 0.3.0.
Phase 2: a language that talks to the world (0.4)#
Goal: services and clients without leaving the language.
- Sockets in the runtime: TCP and UDP, blocking, with timeouts; the
blockseffect on every call. std.net: addresses, DNS lookup, TCP client and server helpers.std.http: a client (HTTP/1.1, chunked, redirects) and a small server (routing, static files) on top of sockets. TLS through@cImportof a C library (libtlsor OpenSSL), documented, optional.- Status: sockets,
std.net,std.http,std.thread(threads, channels, mutexes) andexamples/service.nxare done; TLS stays optional through@cImport. The async decision is taken: threads and channels, no colored functions (decision 82).std.processcaptures output, feeds stdin and sets the working directory. Phase 2 is complete; POSIX signals are left for a program that needs them. - Threads (spec 7.2): spawn, join, channels with move semantics,
Mutexas aref class;shared_mutablebecomes the effect that gates them. - Process:
process.rungains stdin/stdout capture, environment, working directory; signals on POSIX. - Decision needed: async. The spec does not have it. Phase 2 decides between blocking threads only (simple, matches the effect system) and a structured concurrency design. Default answer unless a strong case appears: threads and channels, no colored functions.
Exit: examples/server.nx, an HTTP server serving JSON from a directory, and examples/client.nx fetching and parsing a URL, both under test.
Phase 3: a language other people can build on (0.5)#
Goal: sharing code beyond a sibling file.
- Packages: a
nexium.tomlmanifest,import pkg.module, versioned dependencies fetched from git tags first (no registry required), lock file, vendoring. Status: done (nx init,nx add,nx fetch,docs/packages.md). nx publishand a minimal registry (static index in a git repository, the way early Cargo worked), only once there are packages to publish.nodeartifact:nx shipto an npm package, since Python and Node together cover most "one native piece" needs. Done, through koffi rather than N-API (decision 86).installerartifact:nx shipproducing an installer for a Nexium program, reusing this repository's Inno and script templates. Done.- Editor support: tree-sitter grammar for Neovim, Helix, and Zed, and the language server's completion, go-to-definition and rename: done.
- Linguist PR, when the usage bar is met.
- Status (0.5.0): everything above is done except
nx publishand the registry, which wait for packages to exist, and the Linguist PR.
Exit: a second person's package is used by a third person's program.
Phase 4: the compiler in Nexium (0.6 to 1.0)#
0.6 opens with the syntax settled for 1.0 (decision 87): if c { }, while c { }, for x in items { }, if let v = opt { }, braces always, no semicolons. nx fmt migrates 0.5 sources. Nothing else about the surface syntax is planned to change before 1.0.
Goal: nx built by nx. Runs in parallel with phases 1 to 3; each stage is checked against the Rust compiler on identical inputs.
- Parser (
self/parser.nx): id-arena AST, dumped in the shape ofnx sexp, diffed over every example and std module. Done in 0.6.0: identical on all 46 sources, enforced bycargo test. - Checker (
self/check.nx): the largest stage. Types interned in aList, effects by fixpoint, ownership, monomorphization, dumped in the shape ofnx tirand diffed over every source. Done so far: declarations and signatures (--sigs), then bodies: statements, expressions, calls, builtins, matches and patterns, casts, moves, ranges, generics, closures, records, trait objects, binary patterns, the compile-time interpreter, the diagnostics passes (effect bounds, exhaustiveness, escaping views),@cImport(self/cimport.nx). Done: identical on every source (54,check.nxitself included), every compile-fail case rejected with the same messages. - C emitter (
self/cgen.nx): byte-identical C for every example. Done: identical on every source in the tree, its own included. - Driver, tools, and the std embedding in Nexium. Done:
self/nx.nx(build, run, test, check, emit-c, tir) with the standard library embedded; the other tools (fmt, doc, lsp, ship, packages) stay in Rust for now. - Bootstrap: Rust
nxbuildsnx1;nx1buildsnx2;nx1andnx2produce identical output. Done for the emitter (cargo testruns the three stages); the driver's tools come next, then the Rust compiler moves tobootstrap/, kept for building the first Nexium compiler on a fresh machine. - Status (1.0.0): the compiler, every tool, the test harness and the fuzzer are Nexium; releases ship the Nexium
nxbuilt from the C seed; the stability policy and the platform tiers are written; the Rust crate is gone.
The rest of the climb, one release each (decision 90):
- 0.8, the Sickle. The compiler in Nexium is the compiler: language changes land in
self/and only there. The Rust crate is frozen at 0.7 semantics and moves tobootstrap/rust/; it still builds, and is still the binary users run, because the tools are in it. The seed for a fresh machine is the C the Nexium compiler emits for itself, checked in asbootstrap/nx.c: a C compiler buildsnx0,nx0buildsself/nx.nx, the result builds itself, and the two agree. CI does this withoutcargo. The language suites (examples, spec, compile-fail) run through the Nexium-builtnx; the oracle diffs that scaffolded the port retire. First change made in one compiler only:unbounded_stack. - 0.9, Summit Ridge. The tools in Nexium:
fmt,doc,lsp,ship, packages, the REPL, the migrator, the installer generator,doctor,version. The shippednxbecomes the Nexium one. Nothing else new: the stability policy, fuzzing, the tier list. - 1.0, Summit.
bootstrap/rust/is deleted. The repository is Nexium, one generated C file, and the runtime header. Done.
Exit: cargo is no longer needed to build nx from a release tarball (0.8), and no Rust is left in the repository (1.0). Both reached.
Phase 5: 1.0#
A 1.0 means the language stops changing under people's feet.
- The specification in
SPEC.mdis complete and every section has tests. Done:tests/specholds one recorded program per claim in sections 2 to 14, diffed through every self-hosting stage; the artifact and toolchain sections (15, 16) are covered by the ship tests. - Regions: the full rule set (R1 to R4 in the archived design) or an explicit decision to keep R1 only, with the escape cases documented. Done: R1 only (decision 88), the uncovered cases listed in SPEC 5.6.
- Layouts:
packedandsoaimplemented or removed from the spec. Done: removed (decision 88); the spellings are errors. - Allocation strategies:
poolandstackimplemented or removed. Done: removed (decision 88). - Stability policy: what a minor version may change, deprecation cycle,
nx fixfor mechanical migrations. Done (0.9):docs/stability.md,nx fix. - Platforms: Linux aarch64 and Windows arm64 in releases; a tier list. Done (0.9):
docs/platforms.md; both cross-compiled in every release. - Security: a disclosure process is in
SECURITY.mdalready; add fuzzing of the parser and the binary pattern engine to CI. Done (0.9):tests/fuzz.nxon every push. - C toolchain support: which Zig versions
nxis tested with, in CI and in the docs. Done (0.9):docs/stability.md, "The C toolchain". CI pins 0.14.1; 0.16 differs in what its debug build traps and in which Apple SDKs it links against, and 0.7.0 found bugs that only one of them showed.
Every item is done: 1.0 is the release that deletes bootstrap/rust/.
Past 1.0#
What 1.0 is not yet#
The four things a careful reader will hold against 1.0, and where this roadmap answers each:
| weakness | where it is answered |
|---|---|
Memory safety is not guaranteed: ownership without a borrow checker still lets a view outlive its storage (two were found in std and the compiler under AddressSanitizer while the tutorial was written) | 1.2, the whole theme; until it lands the docs say "memory-safe by default" nowhere |
| Maturity: writing one tutorial found eight compiler bugs; a stranger will find more | the hardening commitments under Always below: the corpus, the fuzz budget, the sanitizer job, a patch within a week of a fix |
| No performance numbers: it compiles through C, which is not the same as a table | the numbers page, pulled forward from 1.6 into 1.1 |
| No ecosystem: sixteen std modules, no registry, no third-party packages, one maintainer | 1.4 (std), the registry and the second-maintainer items under Ecosystem; async is answered under Not planned, errors with payloads under 2.0 candidates |
Where the four stand at 1.5.0: memory safety shipped in 1.2 (the view rules, errors since 1.3); the numbers page is up, measured every week and at every release, and the README's Speed section carries its table; maturity is the hardening under Always, which goes on; the ecosystem has twenty-nine std modules and four projects outside the tree, and still one maintainer and no registry.
After 1.0 the language changes only by addition, under the stability policy of phase 5. Each minor version has a theme; a bullet moves into a version when it has a decision entry and a test plan. The compass sentence still decides the order: what the compiler and the tools in self/ and std/ needed first, what other people's programs need next. Everything here was found by writing Nexium, not by reading other languages' feature lists.
Before anything: the open bugs, and 1.0.3#
KNOWN_ISSUES.md is this roadmap's page zero: every open bug lives there with a reproduction and the fix it needs, a fix removes the entry, adds a Fixed line and a regression test, and the next patch release ships it. Nothing below is started while a known bug that a user can hit sits there. (1.0.2 was the hotfix for the release binaries, which were built for the runner's CPU.) The four entries an outside review of 1.0.1 added shipped in 1.0.3:
- A contained panic leaked what the call acquired: the export wrapper's
longjmpskipped every drop between the panic and the boundary. A per-call tracker in the runtime now releases live allocations, open files, sockets and held locks on the panic path; it is also the first half of 1.2's "a panic releases what it owned". nexium.lockwas written and never read, so the lockfile pinned nothing:nx fetchhonours it,nx updateis what re-resolves.- The Python wrapper took a list for a
[]mut Tparameter and dropped the writes: mutable slices accept only writable, contiguous, matching buffers. - Effect notes pointed at the function rather than the recorded witness; they point at the witness, and the compile-fail cases pin the line and column.
With them, the words the same review found wrong, also done: SECURITY.md said "memory-safe" in one sentence and listed safe-code use-after-free cases in the next; until 1.2 the claim is "deterministically memory-managed without a garbage collector, memory safety complete in 1.2", everywhere. The README says "language-stable, early ecosystem" near the top. And for discovery: "Nexium language" in every page title, and nexium-lang for the language's own packages on PyPI and npm when they come, since nexium is taken there by unrelated projects (decision 91).
First: the quick wins, in order#
Everything below is described in a theme further down; this is the same work sorted by how soon it can ship. None of it changes the language, so each lands as a 1.0.x patch or in 1.1 the day it is done, in roughly this order. The themes after this section keep their order; this is the queue in front of them.
Hours each.
nx doctorcompiles and runs a one-line program before it says "everything works": the checker, the C emitter, the C compiler, the linker and the executable all have to answer, and an illegal instruction from the probe is named as such with the fix. 1.0.1's doctor reported a working installation on a machine where the compiler could not compile a line, because it had only asked the C compiler for its version. The harness runs the check. (Shipped in 1.0.3.)- The installer refuses to run twice (a setup mutex naming the one already open), closes a running
nxbefore replacing files, and writes a log; the uninstaller the same. (Done, for the compiler's installer and the onesnx shipwrites.) - The one-C-file install on the front page of the docs, and
install.shfalling back tocc bootstrap/nx.cwhen there is nothing to download. (Done;NEXIUM_FROM_SOURCE=1asks for it.) - The release workflow writes a
.torrentwith the assets as web seeds and puts the magnet link in the notes; a QR code beside it. (Done: one torrent per file, from 1.0.4 on.) - A Homebrew tap, a Scoop bucket and the winget manifest, from the archives the release already builds. (Done: the repository is the tap and the bucket,
scripts/packaging.pywrites all three at each release; the winget submission to microsoft/winget-pkgs is a pull request by hand.) nx doctorsays when a newer release exists. (Done.)- The VS Code extension on Open VSX beside the Marketplace. (Done in the release workflow: both publishes run when
VSCE_PATandOVSX_PATare set; the tokens and the namespaces are the maintainer's to create, seeeditors/vscode/README.md.) - A
packages.md: the packages people cannx addfrom git today. (Done, as a section ofdocs/packages.md: the honest list is the Topo's two example packages, and how to get a row.) - An "Open REPL here" folder entry and a Windows Terminal profile from the installer. (Done.)
ghcr.io/londopy/nexium: the compiler with Zig, on Alpine and Debian. (Done:docker/, published by the Docker workflow at each tag, for amd64 and arm64; CI builds and runs both on every push.)
A day or two each.
install.ps1, the PowerShell one-liner, and the Chocolatey package that wraps it. (Done; the package wraps the installer with its checksum, as Chocolatey's moderation asks, and the push needs the maintainer'sCHOCO_API_KEY.)- REPL
:undo,:saveand:load;:effects expr. (Done;:loadwas there already.) nx -eandnx -pone-liners on the REPL's compile cache. (Done, on the interpreter: nothing is compiled, so there is no cache to keep.)nx layout Type: offsets, sizes, padding, the reordering. (Done:nx layout FILE [Type...].)expect_snapshotinstd.testing. (Done, besidesnapshot.)pip install nexiumandnpm install nexium, wheels and packages that carry the binary. (Done asnexium-lang, decision 91:scripts/pypi_npm.pybuilds them from the release archives, the release attaches them and uploads whenPYPI_TOKENandNPM_TOKENare set.)- The installer detects an installed version: upgrade, repair, remove, the previous choices as defaults. (Done in the compiler's installer; the ones
nx shipwrites keep Inno's own upgrade-in-place.) nx upgrade, andnxupbehind it later. (nx upgradedone: the release for the machine, verified, over the running executable; a separatenxupwhen one is needed.)- Portable mode, and
nx installfrom a portable copy. (Done: aportablemarker keeps Zig's cache beside nx,nx install [DIR]copies the folder into the user's place and onto the PATH.) - The effects lockfile (
nx audit --lock) and the CI check. (Done:file.effects.lock,--checkin CI on the shipped example.) - Panic proofs as code lenses in the language server. (Done.)
- Colour as you type and completion in the REPL, from the server. (Done: a line editor on raw terminal input, the server's completion at the cursor, history;
NX_PLAIN=1for the plain reader.)
About a week each.
nx explain f effect: the provenance tree. (Done.)- The numbers page:
bench/in four languages, on a fixed runner, published. (Done:bench/in Nexium, C, Rust, Go and Python, the Bench workflow on a GitHub runner weekly and at tags, the medians ondocs/numbers.md, a regression a failing job.) nx test --watchandnx run --watch(file watching in the runtime). (Done, polling the program's files twice a second; a watcher in the runtime can replace the poll when one is wanted.)- The ownership trace,
nx run --trace ownand the REPL's:own. - The binary-pattern debugger,
nx bin. :showand:plotin the REPL: the inspector and chart windows on nexium-gui.- The installer's editor page, and the wizard in six languages.
- Authenticode signing and macOS notarization in the release workflow (the certificates are the slow part).
Everything longer, in the order of the themes: memory safety (1.2), incremental builds and the semantic language server (1.3), std.tui and nx topo (1.4), WebAssembly and the 32-bit targets (1.5), hot reload, profiling by effect, the visual tools (1.6), the seam (1.7), nexium-gui grown up and the Hut (1.8).
1.1: the language the compiler wanted#
Ergonomics the self-hosted compiler paid for by hand.
?Tchaining:a?.bisnullwhenais (deferred since phase 1). (Done: the rest of a chain applies to the payload,a?.name.len.)- Tuple destructuring:
let (a, b) = pair, inforbindings too. (Done: views over a place, owners over a temporary, decision 92.) derive(Clone)for structs and enums whose fields all clone, the way drops are derived (KNOWN_ISSUES: every deep copy incheck.nxis a hand written function). (Done, decision 93;check.nxlostcv_cloneandframes_clone.)- Iterators:
for x in vover any value with anext(self: *mut Self) -> ?Tmethod, so a user type iterates like a slice;Mapiteration over entries(k, v)as well as keys. (Done, decision 94.) - Slice patterns:
[first, rest..],[a, b], andx @ patbindings inmatch. (Done, decision 95.) - Range facts through
whileconditions andelsebranches (KNOWN_ISSUES), and proofs forx / yaftery != 0,.?afterx != null, and slice bounds afterif s.len >= n, so fewer functions carrypanics. (Done, decision 96.) - Format width from a value (
{:>w}) and named placeholders with an anonymous literal (format("{x}", .{ .x = 1 })). (Done, decision 97.) - Intrinsics still missing from a systems language:
@bitCast,@min,@max,@alignOf,@target()(os, arch, pointer width) for compile-timeifin std. (Done, decision 98.) unbounded_stack: the spec says reserved. Either a recursion-depth proof (a function is bounded when every recursive call is on a strictly smaller argument) or removal, the way decision 88 settled the others. (Settled, decision 99: the effect stays as the call-graph fact it has been since 0.9, with no depth proof; "reserved" was stale.)- The numbers page (pulled forward from 1.6):
bench/, the programs the examples already implement written the same way in C, Rust, Go and Python, run by CI on a fixed runner, the medians published on the site with the compiler versions and the machine, and a regression a failing check. Before 1.2, so the memory-safety work is measured against a number rather than a feeling.
Exit: the compile-time interpreter and check.nx lose their hand-written copies and index workarounds; nx audit self/check.nx reports fewer panics than in 1.0.
Status: shipped as 1.1.0 on 2026-09-21, every item. check.nx lost cv_clone and frames_clone to derive(Clone), and the share of its functions carrying panics fell from 549 of 589 (1.0.3) to 551 of 611 (the new proofs against the new code); the rest index a list by a computed position, which no guard proves.
1.2: memory safety without a garbage collector#
Status: shipped as 1.2.0 on 2026-09-21. V1 to V5 are in the checker as warnings (decisions 100 to 104), --strict makes them errors, @escape(v) is how a value leaves an arena block, every unsafe block in self/ and std/ carries its reason, and the fuzzer runs mutants in debug mode. Left for 1.3: the warnings become errors, and nx fix inserts .clone() where that is the fix. (Done in 1.3: the rules are errors, and nx fix makes V4's .clone() and V5's @escape, each kept only when a check of the edited program agrees, decision 111.) The rules found three bugs in the compiler on their first run (changelog, Fixed).
The promise of section 12, no undefined behaviour in safe code, has four holes that 5.6 and 5.7 hand to the programmer: a view stored past the storage it points into, a view kept across a growth of its container, a view into a value that is then moved or dropped, and a value that leaves its arena. 1.2 closes them, so that ordinary safe Nexium code cannot create a dangling view and unsafe marks everything the compiler cannot prove: memory-safe by default, with explicit escape hatches, the guarantee Rust gives, kept in Nexium's simpler shape.
Not a garbage collector. A GC would simplify the language and weaken several of its defining promises at once: predictable destruction, visible allocation behaviour, !allocates, embedding in C, Python, Rust and Node processes, no substantial runtime. And not a borrow checker with lifetime annotations either: the language keeps ownership without one. What it keeps, and what it adds:
- Move semantics and destruction at scope exit stay as they are; so do reference counting for shared
ref classobjects andweakfor cycles. - The checker learns where every view points (its origin: a local, a parameter, a container, an arena) and, per function, whether the view is still used later (liveness, not lifetimes). The rules are decided one entry each and enforced by the same pass:
- V1 (R1 today): a function may not return a view into its own locals.
- V2, storing: a view may be stored in a variable, a field, an element or a closure capture only when the storage it points into outlives that place;
out = s[..]withsa local of an inner block is an error, and so is a closure capturing such a view and escaping. - V3, growth: while a view into a
List,StringorMapis live, the container may not be grown, cleared, reassigned or moved:append,insert,put,clear,=and passing it by value are errors until the view's last use. The same for*mut Tpointers obtained with&mut. - V4, moving: a view into a value is dead once the value moves or drops; a use of the view after that is the
use after moveerror the language already has, applied to views. - V5, arenas: a value allocated inside
using arena { }may not be stored in a place that outlives the block: not returned, not assigned outward, not appended to an outer container. The decision entry settles how results leave a block (a heap copy made explicitly, or numbers and other values that carry no allocation).
unsafeis required for what the checker cannot prove: raw pointers from@cImport, casts to*mut, a view the programmer knows outlives its origin. Everyunsafeblock instd/andself/carries a reason comment, andnx auditlists them with it.- Threads and data races stay outside the promise (section 13): memory safety is not race freedom. The scoped threads of 1.4 (joined when the block ends, no handle escapes) remove the one way a thread could outlive the storage its argument points into.
- Leak detection stays. Reference cycles that
weakdoes not break may still leak; a leak is not a memory-safety violation, andnx leaksreports it. - The diagnostics name the origin and the moment it dies: "
outkeeps a view intos, which is dropped at the end of the block on line 12; clone it, or declareswhereoutlives".nx fixinserts the.clone()where that is the fix.
Rolling it out under the stability policy: undefined behaviour was never promised, so a program that had it may become an error in a minor (docs/stability.md). Each rule arrives as a warning in one release and an error in the next, so a codebase gets a release to run nx fix.
Exit: sections 5.6 and 5.7 list no case that is the programmer's responsibility; SECURITY.md extends its promise to views; every rule has a compile-fail case and a spec case; the fuzzer gains a hunter that compiles mutants in debug mode (freed storage filled with 0xDD) and runs them, so a dangling view that slips through is a finding rather than luck.
1.3: the toolchain grown up#
Status: shipped as 1.3.0 on 2026-09-24. Every item below is in; three are narrower than written, and their notes say how: nx fix makes only the edits that keep what a program does (decision 113), std's platform code leaves the runtime's #ifdefs with 1.6 (moved on from 1.5), and the rebuild of "well under a second" everywhere waits for checking by module.
- First,
self/check.nxsplit by responsibility (declarations, types, ownership and moves, effects, patterns, generics, the compile-time interpreter, diagnostics), even while every module still compiles into one unit: twelve thousand lines in one file is the ceiling the rest of this theme would otherwise hit. (Done:check.nxkeeps the checker's state and core, 3,700 of 15,000 lines, and tencheck_*.nxmodules hold the rest asimpl Checkerblocks, decision 110; the compiler emits the same C and typed IR for every program before and after.) - Incremental builds: one C file per module, compiled separately and cached by content hash, so a one-line change does not recompile a 100k-line translation unit; parallel checking of independent modules. (Done for the C, decision 116: a large program's debug build is a C file per module, the unchanged objects reused and the rest compiled on threads; the compiler rebuilds after a one-line change in 0.99 seconds on Linux and 1.75 on Windows, from 7.8. Checking is still whole-program, 0.74 of those seconds: checking by module, and with it "well under a second" on every platform, is the next step.)
#linedirectives in the generated C, so a debugger shows.nxlines, andnx debuglaunching lldb or gdb with formatters forList,String,Map, slices and optionals. (Done: debug and sanitized builds carry them; the formatters also show arrays, error unions and structs, and the CI jobdebuggerchecks both debuggers.)- A semantic language server: hover, diagnostics, go-to-definition and rename backed by the checker's typed IR, not the parser (decision 84 was syntactic on purpose, for 0.5). (Done, decision 115: when the program checks, definition, rename across files and hover come from the typed IR; the syntactic answers remain for code that does not check.)
nx fixapplies the compiler's own hints:+%/+|where the note suggests it,.clone(),_ =, the view rules' fixes from 1.2. (Done, decisions 111 and 113: the checker offers an edit with an error andnx fixkeeps it when a check of the edited program agrees; V4's.clone(), V5's@escape, and_ =.+%and+|stay the programmer's choice: each changes what the overflowing case computes.)nx bench:bench "name" { }blocks with warmup, iterations and medians, in the same file as tests. (Done: an optimized build, the iterations calibrated to a 10 ms sample, 21 samples, the median with the fastest and slowest; the block's value is kept from the optimizer.)nx build --sanitize address,undefinedthrough the C compiler, andnx test --sanitizein this repository's CI. (Done:build,runandtesttake it;addressneeds gcc or clang, and through zig a sanitized optimised build is compiled at-Og, where zig's UBSan reports rather than traps. CI runs every std module's tests under both.)- Conditional compilation:
if comptime @target().os == "windows" { }in std replaces the runtime's#ifdefs one by one. (Done for the language, decision 114:if comptime @target().0 == "windows" { }builds one branch. Moving the runtime's#ifdefs into std goes with 1.6's runtime work, moved there from 1.5's platforms; the process, socket and thread code is where they are.)
Exit: a one-line change rebuilds in well under a second on the compiler's own sources, and the language server answers from the checker.
1.4: a standard library people stop supplementing#
Status: shipped as 1.4.0 on 2026-09-25. Every item below is in; two are narrower than written, and their notes say how: the scoped threads are calls, thread.each and thread.both, where a block that joins when it ends would need a destructor the language does not have, and text.chars keeps its u32s as 1.x promises, with text.scalars giving the chars beside it. The exit criterion holds: examples/tool.nx, service.nx and the compiler use the library where they wrote their own (the compiler checks its downloads with std.hash and reads GitHub's answers with std.json, the tool counts in a Map).
- Collections:
Set(T),Deque(T),std.sortwith comparators and stable sort,std.heap(priority queue), binary search on sorted slices. (Done:std.set,std.deque,std.sortwithlower_bound,upper_boundandbinary_search,std.heap, written in Nexium over the slice's newswap.) std.path(split off fromstd.fs),std.env(config files, XDG and AppData directories),std.csv,std.toml(the manifest reader moves onto it),std.base64,std.hash(FNV, SipHash forMap, SHA-256 for checksums),std.uuid,std.logwith levels and structured fields. (Done:std.path,std.env,std.csv,std.toml, which the compiler's manifest reader now uses,std.base64,std.hash(FNV-1a 64, SipHash-2-4, SHA-256),std.uuidandstd.log;random.secure, from the operating system's generator; andMaphashed with SipHash-1-3 under a key drawn once per process, keeping its keys in the order they were first put, decision 121.)std.httpclient with redirects, timeouts and streaming bodies, and HTTPS through a TLS slot (decision 120). The client speaks HTTP over any stream, and one interface turns a TCP connection into an encrypted one: connect with a deadline, send, receive, close, and whether the server ended cleanly (close_notify) or the connection was cut. Two layers fill the slot, in this order (the client, the slot and the first layer done:http.Client,Streamingand the traithttp.Transport, which nxtls fills in a few lines, checked against Discord, Google, example.com and GitHub; nxtls ships that adapter itself once 1.4.0 is out, with its randomness fromrandom.secure):- First, nxtls: the TLS 1.3 client written in Nexium (Londopy/nxtls), the one nexium-discord talks to Discord through. No C, the same on every platform, every byte tested against Python's
cryptographyand OpenSSL's server, and reviewed and fixed on 2026-09-25 (0.4.0: a bounded chain search, a cut connection told from a closed one, one deadline for the whole handshake, IP addresses). It speaks TLS 1.3 with ChaCha20-Poly1305 over X25519 and nothing else, which every large host tried accepts: Discord, GitHub, Google, Cloudflare, 1.1.1.1 and 8.8.8.8. It stays a package, which a program hands to the client. Once 1.4.0 is out it takes its randomness fromrandom.secureand runs on Windows too; it reads /dev/urandom today. - Then, the platform's TLS: SChannel on Windows, Security.framework on macOS, OpenSSL where the system has it, behind the same interface, as std's own
https, which needs no package. It reaches the servers nxtls cannot. www.echolink.org, probed on 2026-09-25, answers every TLS 1.3 ClientHello with a handshake_failure alert, and on TLS 1.2 takes only ECDHE-ECDSA-AES256-GCM-SHA384 over P-256; none of that is in nxtls, and stunnel was the way there. (Done:http.SystemTlsovernet.tls_*, each platform's library loaded when first used, so no program links one;http.get("https://...")needs nothing else. Certificates are checked against the system's roots and the host's name, and a refusal says why.) - Why both, not one: the platform alone puts C over three operating systems' APIs under every HTTPS call, each with its own certificate store and its own errors, and leaves nothing that behaves the same everywhere. nxtls alone could not reach a server like echolink without TLS 1.2, constant-time AES-GCM and constant-time P-256 key exchange written in Nexium: weeks of careful cryptography for old servers the platform already reaches.
- First, nxtls: the TLS 1.3 client written in Nexium (Londopy/nxtls), the one nexium-discord talks to Discord through. No C, the same on every platform, every byte tested against Python's
- Discord in the language, on top of the TLS slot:
std.websocket, a client with the handshake, frames, ping and close (a Discord bot, and every gateway like it, needs one), and the second package from the wild beside statusmith's presence SDK,discord: the gateway (identify, heartbeat, resume), the REST calls a bot needs (messages, slash commands, embeds, reactions), the events as an enum tomatchon, and the token from the environment. The Topo's Discord bot chapter (below) is its test, and statusmith's SDK grows the same way as it needs to. (Done:std.websocket, a Discord app's handshake, frames and decoder over std.http's transports,ws://andwss://, checked against Discord's gateway over nxtls; and thediscordpackage, Londopy/nexium-discord: a Discord app's gateway state machine behind aBotwhosenextreturns events tomatchon, the REST calls with rate limits waited out, messages, interactions and slash commands, nxtls in the slot. The Topo chapter is still to write.) std.text: grapheme clusters and case mapping tables,chars()over scalars, width for terminal alignment. (Done: extended grapheme clusters (UAX #29) and full case mapping and folding from Unicode 16.0's tables, generated byscripts/unicode_tables.py;scalarsgiveschars (charskeeps itsu32s, as 1.x promises);widthby grapheme cluster withpad_leftandtruncate_width. Segmentation agrees with ICU but for Unicode 17's data changes, case mapping with Python's.)std.time: time zones from the platform database, ISO 8601 parsing in both directions,Durationarithmetic. (Done:time.zone(name)reads the zoneinfo files on Linux, macOS and the BSDs, TZif with the POSIX rule at their end, and ICU on Windows through one runtime call,time.zone_rules;local_zone, wall clocks to instants across the changes,%Z; week and ordinal dates and the basic format both ways;Durationcompares and does arithmetic, and reads and writesPT1H30M. Checked against each other: ICU and the zoneinfo files give the same offset for 30 zones every week from 1850 to 2150, except where Windows' copy of the database is older.)std.process: pipes as streams, signals, exit codes by name. (Done:process.startruns a program alongside, its stdin aPipeWriterand its stdout and stderrPipeReaders read by line or chunk as the output comes, each stream a pipe, this program's, nowhere, or stderr merged into stdout;wait,wait_for,kill,terminateandsignal, with timeouts on every wait; while it waits for one thing, the runtime keeps what arrives on the other pipes, so a child never stalls on a full one.Statusnames signals and exit codes (SIGTERM,EXIT_USAGE, sysexits and the shells' 126 and 127), andtrap_signalsturns SIGINT, SIGTERM and SIGHUP (Ctrl-C, Ctrl-Break and the console closing on Windows) into values to wait for. The stall ofprocess.execon a child that writes before it reads is fixed as well. Windows has no signals:signalends the program there, and its status still says which signal.)std.thread:selectover channels, scoped threads that are joined when the block ends (no handle can escape), atomics insync. (Done:select2over two channels of any types andselectover any number of one type, on bells the channels ring, andrecv_forwith a timeout;thread.eachandthread.both, whose threads all end before the call returns, so no handle escapes and what they point into outlives them (a block that joins when it ends needs a destructor or a block form the language does not have; a call gives the same guarantee), a panic raised once all have ended;Atomicoversync.atomic_*. Clean under ThreadSanitizer.)std.testing: property-based tests (check(gen, fn)) with shrinking, the same driver the fuzzers use. (Done:testing.check(T, gen, holds)over generators written againsttesting.Rng, which keeps every choice, so shrinking works on the choices and needs nothing from a generator;searchbeneath it runstests/fuzz.nx, whose findings are shrunk before they are saved. The course's testing chapter has a property.)
Order, with the collections, std.hash, random.secure, the small modules (std.path, std.env, std.uuid, std.log, std.csv, std.toml, std.base64), Map on SipHash, std.time (and with it the known issue of import std.time hiding time.now() fixed), the HTTP client with the TLS slot and nxtls in it, std.websocket, the discord package, std.process, std.thread, std.text, std.testing and the platform's TLS all in.
Exit: examples/tool.nx, service.nx and the self-hosted compiler import nothing they had to write themselves.
1.5: platforms#
Status: shipped as 1.5.0 on 2026-10-04, and the exit criterion holds: one source runs in a browser, on a Raspberry Pi (ARMv7 and ARM64) and on the three desktops, and docs/platforms.md says which combinations CI proves. In: WebAssembly programs (wasm32-wasi), which run every spec case, example, standard library test and Topo program under Node.js in CI, a panic unwinding as it does natively (decision 123); artifact wasm, a library as a module with a JavaScript loader, which CI loads into a page in headless Chrome (decision 124); the 32-bit targets (decision 122), the same suites run for i686 on CI's machines and for armv7 and riscv32 under QEMU, the compiler building itself as a 32-bit program, and every release carrying nx for 32-bit Windows and Linux and ARMv7 Linux (tier 2); Linux and Windows on ARM at tier 1, the test job running on GitHub's arm64 machines. The rest moved to the themes it belongs with: the runtime's platform code into std, the runtime split, the embedded targets and wasm32-freestanding, static 64-bit Linux builds and FreeBSD to 1.6; the cross-compilation matrix of nx ship to 1.7; nexium-gui's X11, Wayland, Cocoa and canvas backends, and the playground's C backend and share URL, to 1.8.
- WebAssembly:
--target wasm32-wasiandwasm32-freestanding, the runtime's process, socket and thread code behind@target(), and anartifact wasmproducing a.wasmwith a JavaScript loader. The self-hosted compiler compiled to wasm is the playground:nxrunning in a browser, no server. (Done:wasm32-wasiandartifact wasm. Moved:wasm32-freestandingand the runtime's code behind@target()to 1.6, the playground's C backend to 1.8; the compiler already runs in the page, through the interpreter, since 1.4.) - nexium-gui: X11 and Wayland, Cocoa backends beside Win32; the demo runs on all three and in the browser through a canvas backend. (Moved to 1.8, with nexium-gui grown up.)
- Static Linux binaries (musl), FreeBSD, and the tier list extended; Linux aarch64 and Windows arm64 promoted to tier 1 when CI runs them. (Done: the test job runs every suite on
ubuntu-24.04-armandwindows-11-arm, and the 32-bit Linux downloads are static. Moved: static 64-bit Linux builds and FreeBSD to 1.6.) - 32-bit architectures:
i686Windows and Linux,armv7Linux (the Raspberry Pi OS that is still 32-bit, routers, older phones),riscv32andthumbalongside the embedded targets below. Everything in the language already has a width (isize/usizeare the pointer's,@sizeOfis per target, C'slongis already mapped per platform), so the work is in the places 64 bits were assumed: the runtime's handles and sizes (int64_twhereintptr_twas meant,size_tarithmetic,nx_timeand the file offsets staying 64-bit on purpose), the binary pattern engine's 64-bit segments on a 32-bit word, the compile-time interpreter evaluatingusizeat the target's width rather than the host's,@target().pointer_widthfor std, the C header import mappinglongandsize_tper target, the leak and size reports, and the ABI document's word aboutusizeat the boundary. A--target i686-linux-gnubuild of every spec case and every tutorial program in CI (32-bit executables run on the 64-bit runners), the compiler itself built and self-hosted as 32-bit, and the tiers extended: tier 2 fori686andarmv7, tier 1 when a runner runs them. (Done:i686Windows and Linux andarmv7Linux are tier 2, a download each;riscv32is tested and not released;thumbwaits for the embedded targets.) - Cross-compilation matrix in
nx ship: every target the C toolchain supports, from one machine, tested in CI for the tier-1 set. (Moved to 1.7, with the rest ofnx ship.) - Embedded targets:
-Osbuilds without the runtime's file, socket and thread parts (@target().os == "none"), the first program on a microcontroller. (Moved to 1.6, after the runtime split.)
Exit: a Nexium program runs in the browser, on a Raspberry Pi and on the three desktops from one source, and the docs say which combinations CI proves.
1.6: the runtime that release builds deserve#
Status: under way on main. The first measurement, with the same C compiler on both sides, found three of the four benchmarks (fib, nbody, sieve) already at C's speed in fast mode; the gap is words, maps and strings, which has gone from 3.0 times C to 1.6 on Windows and 1.6 to 1.8 on Linux, where the figure moves with the run (get_or_put, integers formatted in 64 bits, a string's first block of 16 bytes, small freed blocks kept for reuse). Safe mode's cost is the overflow checks on calls, 1.5 times C on fib. The fifth benchmark, trees, binary trees of ref class nodes added for the ARC elision item, runs at 1.3 times C in both modes on Windows, Rust at 1.1 and Go at 0.6 with its collector.
- ARC elision: retain/release pairs the checker proves redundant (a
ref classpassed down and back within one function) are not emitted; therefcountseffect reports the ones that remain. (Thetreesbenchmark, binary trees ofref classnodes, is its measure, decision 127:countbinds each child withlet, a copy retained and released, twice per inner node, asnx refcounts bench/trees.nxlists; bound withif let, which borrows, the program runs about a tenth faster, the elision's ceiling.) - Bounds-check elimination in loops from the range analysis;
nx auditshows which checks survive in a hot function. Map: a hash chosen per key type (the iteration order is documented since 1.4: the order the keys were first put); small-string optimization forString;Listgrowth policy documented and tunable perusingblock. (Done:Map.get_or_put, an update in one lookup, decision 125; an integer written with 64-bit arithmetic where each digit was a 128-bit division; a string's first block 16 bytes, one allocation for a short string instead of two. Small-string optimization is dropped: it does not fit how aStringis passed, by copy, with slices into it allowed to outlive the call. In its place the runtime keeps small freed blocks for reuse, per thread (decision 126), which is what the short strings cost on Windows, whose C library caches nothing small:wordsin three quarters of the time there, 2.1 times C to 1.6; nothing on Linux.)for parallel: work stealing, a chunk size heuristic, and nested parallel loops that share one pool.- Compile time: the checker's monomorphization cache, and preprocessed
@cImportheaders cached by hash. - The numbers page of 1.1 is the measure: each item above lands with its before-and-after row.
Exit: every example in release mode is within a documented factor of its C counterpart, and the factor does not grow between releases.
Platforms, continued (from 1.5).
- The runtime's process, socket and thread code moved into std behind
if comptime @target(), and the runtime split so its allocation, file, socket and thread parts are separable, which the embedded targets and 1.7's#![no_std]crates need. - Embedded targets:
-Osbuilds without the runtime's file, socket and thread parts (@target().os == "none"), the first program on a microcontroller,thumbamong the targets;wasm32-freestanding, a module with no WASI, on the same split. - Static 64-bit Linux builds (musl) beside the glibc ones, for Alpine, and FreeBSD in the tier list.
1.7: the seam, both ways#
Today a Nexium library leaves the tree through nx ship: a C header and archive, a Python wheel over ctypes, a Rust crate with safe wrappers, an npm package. What crosses is what C carries: integers, floats, slices, layout(c) structs, a status code with an error name. Everything else stays home, and the traffic runs one way. This theme widens the seam in both directions, with the same rule as 1.0's spec section 15: a panic never crosses, no initialization call, no process-global state, and a symbol means the same thing from every language.
Python, deeper.
- Strings and bytes as values:
[]u8andStringparameters takestrandbytes, returns come back asstr;[]Stringas a list of them. - The buffer protocol without copies: a
[]f64parameter takes a NumPy array,memoryviewor anything contiguous by pointer, and a[]mut f64writes through; a two-dimensional[][]f64view maps to a C-contiguous array with its shape. Todayarray.arrayandbytearrayare the zero-copy cases and a sequence is copied. - Records, enums and optionals:
layout(c)structs arrive as dataclasses with the same fields, anenumas anIntEnum,?TasNoneor the value, an error set as an exception class per error name (ropesim.InvalidInput, a subclass ofNexiumError), soexceptcan name the one it handles. - Objects: an export that returns a
ref classhands Python an opaque handle whose__del__releases it; the class'simplmethods become methods of the handle, so a stateful library (a parser, a simulation) is a Python class. - Callbacks: a parameter of function type on an export becomes a Python callable; the compiler emits the
CFUNCTYPEshape and the marshalling, and the call carries theffieffect on the Nexium side. - A second wheel shape,
artifact python { mode = "extension" }: a CPython extension module against the limited API (abi3), one wheel per platform, calls an order of magnitude cheaper thanctypesfor small functions; thectypeswheel stays the default because it needs no Python headers to build. - Python inside Nexium:
@cImport("Python.h")works today; apythonpackage (registry, notstd) wraps the C API with typed conversions,py.run,py.call(module, name, args), and the interpreter's lock as ausingblock, so a Nexium tool can call a Python library without shipping a wheel first.
Rust, deeper.
- Error sets as enums:
error Parse { Bad, Truncated }becomesropesim::ParseError { Bad, Truncated }and the export returnsResult<T, ParseError>;NexiumErrorstays for panics and for exports that widen to!T. - Strings:
&strandStringcross as[]u8andString;&[u8]as[]u8; a returnedStringarrives owned, freed by aDropthat calls the archive's free. - Objects: a
ref classreturn becomes an opaque struct withDrop, and itsimplmethods become inherent methods;Sendis derived from the absence ofshared_mutableon every method. #![no_std]crates when no export allocates (the runtime split so the archive's allocation, file, socket and thread parts are separable, which 1.6's embedded targets need too), so a Nexium library can sit inside a Rust firmware.- Source crates: the crate's
build.rsrebuilds the archive from the.nxsources whennxis on thePATH, socargo buildafter a change to the Nexium is enough, and the crate can be published to crates.io as source. - Rust inside Nexium:
nx addof a Rust crate that exposesextern "C"functions builds it withcargointo a static library, runscbindgenfor the header and binds it through@cImport, so a Nexium program can use a Rust crate without writing the bridge by hand.
More languages out. Each is an artifact kind with a wrapper in that language's idiom over the same C ABI, a test in CI that calls ropesim from it, and a chapter in the embedding guide:
- C++: the header compiles as C++ today; add
ropesim.hppwith RAII wrappers over handles,std::spanandstd::string_viewoverloads, and results that followstd::expected. - Go through cgo: a package with the header and archive and Go-typed wrappers, errors as
errorvalues with the Nexium name. - Java and Kotlin through Panama (
jextractover the header) with a small hand-written layer for slices and errors; JNI only where Panama is not available. - C# through P/Invoke: a
DllImportwrapper and a NuGet package with the native archive per runtime identifier. - Ruby through
fiddle, Lua as a C module, Swift through a module map over the header; Zig through@cImportof the header, which works today and needs a page. - The browser: the
wasmartifact, with a TypeScript declaration file generated from the exports. (Done in 1.5, decision 124: CI callsropesimfrom a page in headless Chrome, and the embedding guide has the chapter.)
The ABI itself.
docs/abi.md, versioned: the C shape of every crossing type, the status convention, who frees what, which thread may call what, and how a handle is retained and released, so a wrapper can be written for a language this list does not have.- Buffers that leave: an export may return a
Stringor aListof a crossing type; the header carries<name>_free, the wrappers call it, and the leak detector counts what the host never freed. nx ship --abi-check: the header and the export list are compared with the previous release's and a removed or narrowed export is reported, so the version number of a shipped library follows semver by construction.@cImportgrows the C it accepts: object-like macros with values, function-like macros as inline functions, bitfields, variadic declarations, and a[c]section ofnexium.tomlthat vendors and builds a C library (nx add --c sqlite) so@cImportfinds it.- Every platform from one machine (from 1.5):
nx shipbuilds each artifact for every target the C toolchain supports, the wheels and npm packages of every platform from one runner, tested in CI for the tier-1 set.
Exit: ropesim shipped to C++, Go, Java, C#, Ruby, Lua and the browser and called from each in CI; the Python wheel takes a NumPy array without a copy and raises ropesim.InvalidInput; the Rust crate's error enums come from the Nexium error sets; an example calls a Rust crate and a Python library from Nexium; --abi-check fails on a removed export.
Errors you can fix alone#
An intermediate programmer should be able to understand and fix any problem in their own Nexium code without ever reading the compiler or standard library source. Every diagnostic, including warnings and runtime panics, gets a permanent NX code and a page, and nx explain fills that page in with the programmer's own code instead of a generic example. Messages point at the actual mistake, not just where it was caught, and only suggest fixes the compiler has checked will compile. The standard library documents what every function returns, how it fails, and whether it allocates, and a written reference covers the exact rules of the language. None of this relies on discipline. The compiler can't emit an uncoded diagnostic, every documented example is tested in CI, and every feature's errors are written before the feature is built, so a feature that can't produce a clear error gets simplified. We prove it by breaking known-good programs to confirm that one mistake gives one clear error in the right place, and by timing intermediate programmers as they fix each error with nothing but the message and the page.
Where it starts: a message today names the problem and the line where it was caught, with no code and nothing to open. Assigning to a let says "count is immutable; declare it with var" at the assignment and never shows the declaration, which is the line to change; a var that never changes and a binding never used are not warned about at all. What there is to build on: nx fix keeps an edit only when a check of the edited program agrees (decision 113), nx explain already answers why a function has an effect, the compile-fail cases pin each diagnostic with // EXPECT: lines, the fuzzer compiles mutants of real programs, and SPEC.md is the written reference. Pencilled from 1.6, with every diagnostic coded by 2.0:
- The registry: one file of codes,
NXand four digits, assigned in order and never reused or renumbered; a retired code keeps its page. Errors, warnings and runtime panics draw from it alike. The checker'sfailtakes a registered code rather than a string, so the compiler's own types make an uncoded diagnostic impossible, and every message ends by pointing tonx explain NX####. nx explain NX0042prints the code's page: what happened, why it is an error (the rule, with its spec section), the smallest program that triggers it, the fixes and when to choose each, the related codes, and the terms used, each with a concept page. After a failed build it fills the page in with that build's names, lines and types; run cold, it shows the minimal example. (nx explaintakes a file, a function and an effect today; given a code, or the message's own words, it opens the page.) The language server links each squiggle to its page. This replaces the error index of "The docs, more".- Messages point at the mistake: when the line to change is not where the problem was caught, the message shows both and the chain between them ("
countcan't change because line 2 declares it withlet, and line 3 assigns to it"). Plain words for an intermediate programmer, the programmer's own names first, no compiler internals. One mistake, one error: no cascades. - A fix appears only after the compiler has re-checked that the patched program compiles, as
nx fixdoes for its edits, for a message's help lines too. - The standard library says of every function what it returns, how it fails (its error set, and what each error means) and whether it allocates, the last read from the inferred effects so it cannot drift from the code.
- Every documented example is tested in CI: the docs' code, std's doc examples, and each page's minimal example.
- Every feature's errors are written before the feature is built, from the per-feature template (docs/diagnostics-template.md): a clarity gate, the likely mistakes with their codes, where each is caught and where it was made, the exact message, the explain page, the tests. A mistake that cannot be explained in plain words blocks the feature until its design changes.
- The proof: mutation tests that break known-good programs one token at a time and require one clear error in the right place for each, and timed sessions in which intermediate programmers fix each error with nothing but the message and the page, against a bar set before the test; below the bar, the message or the page is rewritten and tested again.
Tools only this language can have#
The REPL was the first of these: a feature no systems language is expected to have, cheap because the compiler was already there. Each item below is the same shape, built on a fact the compiler already knows (every function's effects, every move and drop, every binary pattern's shape) and offered as a tool. Each is pencilled into the minor it fits; the cheap ones may land earlier as patches to the tools, since none changes the language.
Answers from the effect system.
nx explain f allocates(1.3, done on the way): the provenance of an effect, the call chain fromfdown to the primitive that brings it in, as a tree.- An effects lockfile (1.3, done on the way):
nx audit --lockwrites each function's effects tofile.effects.lock; a change that adds an effect (a function starts allocating, can now panic) failsnx audit --check, and CI, until the lock is updated on purpose. Semver for behaviour. - Proofs in the editor (1.3): the code lens above every function reads
cannot panicorpanics: index at :12(done on the way); hover on a slice index saying which fact proved the bound is still to come. - Profile by effect (1.6):
nx run --trace allocatesinstruments only the allocation sites,--trace blocksonly the blocking calls, and prints a histogram per function: a profiler with no configuration, from the effect system. - Record and replay (1.3): a function without
nondeterministicis pure with respect to time, random and the environment, sonx test --recordknows exactly which calls to capture and--replaymakes a flaky test reproducible.
Answers from ownership and layout.
nx layout Type(shipped in 1.0.3): field offsets, sizes, padding bytes and the reordering that removes them, for any struct;layout(c)structs shown as the C compiler will see them.- The ownership trace (1.3):
nx run --trace ownprints every move, clone, retain, release and drop with its source span, and the REPL's:ownshows them for the line just entered. Ownership without a borrow checker, made visible. - Ask the checker (1.3):
:facts xin the REPL, and a hover in the editor, print the range facts and proofs the checker holds about a value at that point (x < xs.len,y != 0), so "why is this bounds check still here" has an answer.
Binary patterns.
- A binary-pattern debugger (1.3):
nx bin parse_png file.pngruns a<<...>>pattern from the source against a real file and prints which bytes bound to which field, and where a match failed. - A binary workbench (1.4): the same as a terminal view, a hex dump on one side and a pattern on the other, the match updating as the pattern is edited;
:binin the REPL renders a pattern's bytes.
Interactive, in the family of the REPL.
- Hot reload (1.3):
nx run --hotrebuilds on save and swaps the changed functions into the running program through a shared library, keeping the state. The effect system says which swaps are safe (a function withoutshared_mutabletouches no global); the GUI library, being immediate mode, redraws with the newframeon the next tick, so a window is edited live the way a web page is. - One-liners (1.1):
nx -e 'for l in io.lines() { ... }'andnx -p 'expr'for shell pipelines, on the REPL's compile cache, so Nexium takes the awk seat as well as the C seat. - REPL sessions as files (1.1):
:undopops the last line (the session is a program the REPL recompiles),:save f.nxwrites it as a script with the outputs as comments,:load f.nxresumes it,:effects exprprints the effects an expression carries. - Watch mode:
nx test --watchandnx run --watchrerun on save (shipped in 1.0.3, polling the program's files); rerunning only the tests whose dependencies changed, from the module graph the split build keeps, is still to come. - Snapshot tests (1.4):
expect_snapshot(value)instd.testingwrites and compares.snapfiles, the mechanism this repository uses for its own examples, offered to every project. - A Jupyter kernel (ecosystem): the REPL behind the notebook protocol, so a Nexium cell runs beside Python ones; the effect chips as cell badges.
- The playground: the compiler compiled to WebAssembly puts a "run" button on every code block of the documentation site and the Topo. (In since 1.4 through the interpreter, decision 108; the C backend in the page is 1.8's.)
The clickable line: windows, wizards and the REPL#
The installer wizard, the REPL and the GUI library are the parts of the project people touch rather than read. This line grows them together: each item is something to click, type into or look at, and each stands on what exists (the REPL recompiles a session; nexium-gui draws in a window or headless; the installer is a wizard already). Pencilled into minors the same way as the tools above.
The REPL you can look into (1.1, 1.3).
- Colour as you type, completion and signatures from the language server, history search, multi-line editing,
:doc List.appendinline. :show valueopens an inspector window: a struct as a tree, aListas rows, aMapas a table, live while the session continues, so a value is looked at rather than printed.:plot xsopens a chart window for a list of numbers;:watch exprre-evaluates and redraws on every line entered. The REPL and the GUI library, fused.
Run from the editor, everywhere (1.3). Ctrl+B runs the file, the variant tests it, and a diagnostic is a click away, in every editor the repository supports, the same way:
- Sublime Text:
Nexium.sublime-build(done: run, test, check, build, effects; the--> file:line:colline is clickable). Vim and Neovim::makeand the:Nxcommands (done). Emacs:C-c C-rand friends (done). Still to write: VS Code tasks with a problem matcher (tasks.jsonshipped by the extension, so Ctrl+Shift+B works without setup), a Zedtasks.json, Helix key bindings for:sh nx run %, Kate's build plugin target, JetBrains external tools and Notepad++ NppExec scripts as files to import rather than steps to type. - One machine-readable diagnostic format for all of them:
nx check --format shortprintsfile:line:col: error: messageon one line (today's two-lineerror: ... / --> file:line:colneeds a two-line matcher, which Sublime and Vim'serrorformatmanage and simpler problem matchers do not), and--format jsonfor the tools that want the notes and the spans. - The installer's editor page (below) installs the build systems with the syntaxes.
The first five minutes (1.3, 1.8).
- The installer's editor page: a checkbox per editor found on the machine (VS Code, Sublime Text, Vim and Neovim, Notepad++, Emacs, Kate, JetBrains and the rest), each putting the files from
editors/where that editor looks: Sublime's syntax intoPackages\User, Notepad++'s UDL intouserDefineLangs, the Vim files intovimfiles, the VS Code.vsixthroughcode; the boxes are ticked for the editors that are installed and greyed with a reason for the ones that are not. An "Open REPL here" entry in the folder context menu and a Windows Terminal profile; a finish page that opens the first chapter of the Topo. The install script does the same on macOS and Linux (~/.config/sublime-text,~/.vim,~/.config/nvim,~/.emacs.d), asking first. nx upgrade: fetches the new release, verifies the checksum, swaps itself;nx doctorsays when one is available.- A macOS
.pkgand a Linux.deb/.rpmwith the same pages (the.deband.rpmare in; the.pkgmoved from 1.5 to 1.8), and manifests forwinget, Homebrew and Scoop, so the install is one line where people already type them.
The installer grown up (1.3). The wizard is the first thing a Windows user sees of the language, so it should behave like software people pay for, in every situation it can find itself in:
- One at a time: a second setup started while one is running gets a message naming the one already open (a setup mutex) rather than two wizards racing for the same directory; the uninstaller the same.
- Knows what is there: detects an installed version and offers Upgrade (keeps the choices made last time: directory, PATH, components, editors), Repair and Remove; refuses to downgrade without saying so; notices a per-user install when installing for all users, and the reverse, and offers to remove the other first; reads the settings of a previous install for its defaults.
- Nothing in use: finds a running
nx, REPL or language server and asks to close it (or lets the user do it) before files are replaced, with a restart-manager retry instead of a failed copy; warns when a terminal has%LocalAppData%\Programs\Nexiumon its PATH and will not see the newnxuntil reopened. - Checks before it starts: disk space including the bundled Zig, a pending reboot, a PATH already near the length limit, an antivirus quarantine of
zig.exeafter the copy (verifies the files it wrote and says which one is missing), and whethercode,gitand the editors of the editor page are present, so a box is unchecked with a reason rather than failing later. - Choice of toolchain: bundle Zig (default), use a Zig already on the PATH, or download it at the end with a progress bar and a checksum; the compact type without Zig for people who have a C compiler.
- Speaks the reader's language: the wizard in the six languages of
docs/i18n, chosen from the system locale. - Signed: an Authenticode signature on the setup and on
nx.exe, so SmartScreen shows the publisher rather than a warning; the release workflow signs from a certificate in the repository's secrets. - Leaves a trail: a log under
%Temp%named from the wizard's last page, an Installation failed page with the log and a link to file an issue, and rollback of a half-finished install. - For administrators:
/VERYSILENTwith every page's choice as a parameter (/COMPONENTS,/TASKS,/EDITORS,/DIR,/ALLUSERS), an MSI wrapper for group policy, and an ARM64 setup beside the x64 one. - After the install: Check for updates in the Start menu entry and a notice from
nx doctor; uninstall that offers to keep~/.nexium(packages, REPL history) or remove everything; a repair that reinstalls only the files whose checksum differs.
Every road in (1.3 to 1.6). Today nx arrives by the Windows setup, the install script, the portable archives, source, and the GitHub Action. People install software the way their platform taught them, so each road below is a manifest or a package built by the release workflow from the same archives, in the order of how many people stand on it. The community usually maintains the distribution-owned ones (AUR, nixpkgs, Homebrew core); the project's job is a release that makes that easy: stable URLs, checksums, and no post-install step.
- Windows:
winget install Londopy.Nexium, Chocolatey, Scoop (a bucket in the repository), an MSI for group policy, an MSIX for the Microsoft Store, and the ARM64 setup. - macOS: a Homebrew tap (
brew install londopy/tap/nexium) then core, MacPorts, the signed and notarized.pkg, and a universal binary. - Linux:
.deband.rpmwith an apt and a dnf repository so upgrades come with the system's; a PPA, a Fedora COPR and an openSUSE OBS project; the AUR; Alpine (apk, musl builds of 1.6); Nix (aflake.nixin the repository, then nixpkgs) and Guix; Snap; Gentoo and Void templates. Flatpak and AppImage for the Hut, not the compiler. - BSD: FreeBSD and OpenBSD ports once the tiers include them (FreeBSD's place in them is 1.6's).
- Containers:
ghcr.io/londopy/nexium(the compiler on Alpine and on Debian, with Zig), a dev container feature, so Codespaces and Gitpod havenxin one line, and asetup-nexium-style step for GitLab CI. - Carried by other ecosystems:
pip install nexium(a wheel that ships the binary, the way Zig and Ruff ship on PyPI) so a Python project'snx shipwheel builds in its own venv;npm install nexiumandnpx nxthe way esbuild ships; conda-forge; Composer, RubyGems and Cargo carriers only when 1.7's artifacts for those languages exist. - Version managers: an
asdf/miseplugin,pkgx, andnxup, the project's own: several versions side by side,nx +1.0.1 run, anexium.tomlpin honoured automatically,nxup update;nx upgradeabove isnxupfor people with one version. - Editors and services: the VS Code Marketplace and Open VSX (the extension is published to the first; the second is where VSCodium and Gitpod look), the Zed and Neovim registries once the language server is semantic, a pre-commit hook for
nx fmt. - Elsewhere: Termux on Android, the playground in the browser (no install at all), and a bootable "try it" image is not planned.
- For programs written in Nexium: every channel above that takes a binary becomes a target of the
installerartifact (nx ship --winget,--brew,--deb,--docker), so a Nexium program reaches its users on the roads the compiler took.
The niche roads (1.3 onward). The ones people remember:
- The PowerShell line:
irm https://londopy.github.io/nexium/install.ps1 | iex, the Windows twin ofcurl | sh, which does not exist yet: it downloads the portable zip or the setup, verifies the checksum, runs the setup silently (/VERYSILENT /TASKS=addtopath) or unzips to%LocalAppData%\Programs\Nexium, and fixes the PATH for the current session too. The Chocolatey package is this script inside a.nupkg, which is why Chocolatey installs feel like magic:choco installis PowerShell all the way down. - One C file is the installer: the compiler is
bootstrap/nx.c, so on a machine with nothing but a C compiler,curl -fsSL .../bootstrap/nx.c | cc -x c - -o nx -lm -lpthreadinstalls Nexium. No other language can be installed by compiling one file; the docs should say so on the front page, andinstall.shshould fall back to it when there is no download. - Torrents and magnets: the release workflow writes a
.torrentper release with the GitHub assets as web seeds and publishes the magnet link in the notes, the way Linux distributions ship images; the checksums are in the file, so a swarm cannot serve a tampered binary. Also an IPFS pin of the same archives (ipfs://), for the people who have IPFS Companion. - Self-installing
nx:nx installfrom a portable copy performs the setup's tasks without the setup (PATH, file association, Start menu, the editor files), andnx uninstallreverses them; the portable zip and the setup become the same thing with a choice. - Portable mode: a
portablefile next tonx.exekeeps packages, the REPL history and the Zig cache beside it, so a USB stick carries the whole toolchain and leaves nothing on the host. - MSIX with an
.appinstallerfile: a link that installs and then updates itself from the release feed, no store account needed. gh release download Londopy/nexium -p 'nx-*-x86_64-unknown-linux-gnu.tar.gz'documented for the people who live in the GitHub CLI; Ansible, Chef and Puppet modules that wrap the install script and pin a version, for the people who install onto a hundred machines.- A QR code on the release page and the site's install section that opens the install page on a phone, for Termux, and for the person standing at someone else's laptop.
nx topo: the tutorial you can run (1.4; the runner shipped in 1.2.1).
Status: nx topo is in the compiler (self/topo.nx): the route map with progress, nx topo <n> writing a chapter's exercises to work on, check grading them with the compiler, hint, solution, quiz, reset; progress in topo-progress beside the update cache. The site shows the same exercises at the end of each chapter with the solutions folded away, a "done" box per exercise kept in the browser, and the count on the chapter list. Forty-one exercises of five kinds cover chapters 2 to 23 (decision 107), eleven quizzes grade their questions against the compiler where a question asks it (decision 109, nx topo verify), and the exercises run and grade in the page (decision 108). Left: the more chapters listed below, and the playground's share URL (1.8).
- An interactive tutorial runner: opens a chapter, shows its program, lets you edit and run it, checks the output against the recorded one, gives exercises with hints and keeps your progress. In the terminal first, and on the site once the playground (1.4) runs code in the page. The book becomes a course.
The Topo as a course (1.4 for the content and the page, 1.8 for the share URL). The chapters teach by showing; a course asks the reader to do. Every exercise is a file in the repository the harness runs, so the course cannot rot:
- Exercises at the end of every chapter, three kinds, all checked by the compiler rather than by an answer key: fill in the blank (a program with a hole marked
???that must compile and print the recorded output), fix the error (a program with a diagnostic the reader must clear; the Topo's_fails.nxfiles already are these), and write it (a spec of a function and a test file that must pass). Solutions ship beside them, folded away on the site. - Quizzes the compiler grades: "does this function allocate?", "which line moves
s?", "what does this print?", "which effect doesmaincarry?", with the answer checked againstnx effects, the ownership trace and the recorded output, so a wrong answer explains itself with the compiler's own words. Multiple choice on the page, the same questions asnx topo quizin the terminal. - Predict-the-output cards: the code shown, the output hidden until the reader commits to a guess.
- In the page, once the compiler runs in the browser: every code block editable with a Run button, the exercises and quizzes graded in place, a reset to the book's version, and a link that carries the reader's code (the playground's share URL). Until then the same exercises run in the terminal through
nx topo.
The playground, light: the compiler in the page without the C backend (next, before the rest of the course; the full wasm target stays in 1.5).
Status: done (decision 108). nx play, the NX_WASM runtime and site/play.js are in; every fill and fix exercise of chapters 2 to 15 grades in the page, and every other code block with a main runs. The exit check runs every exercise through the WebAssembly build and the page's own WASI layer in Node (CI's playground job and the Pages workflow) rather than a headless browser; the harness's play suite holds the interpreter to the compiled output of every recorded program.
The site is static files on GitHub Pages, so running code there means the compiler itself runs in the browser. Two things make that possible now rather than in 1.5: the compiler is one C file with no dependencies (bootstrap/nx.c), which zig cc -target wasm32-wasi compiles to WebAssembly, and the REPL already runs programs without the C backend, through the compile-time interpreter, println included. So a wasm build of nx can check a program (the real diagnostics, for the fix-the-error exercises) and run it through the interpreter, capturing the output to compare with the recorded one: the whole grading loop, client side, no server. The pieces:
NX_WASM: a build of the runtime with the parts a browser has no equivalent for (threads, sockets, process spawning, the file system) stubbed to a clear error, behind one define.nx play: a command that reads a program from stdin, checks it, runsmainin the interpreter and prints its output or its diagnostics; the standard modules are embedded, so no file access is needed. The same command serves the terminal (nx play < file.nx) and the harness.- The Pages workflow compiles the seed to wasm with the pinned Zig and publishes it beside the site; a small WASI shim in the page's JavaScript feeds stdin and collects stdout and stderr, loaded on the first Run, not with the page.
- On the site: every exercise's code box editable, with Run, Check and Reset; a fill-in-the-blank or fix-the-error exercise is graded in place against its recorded output and ticks its own box; a prediction reveals the real output after the guess; every other code block of the Topo and the docs gets Run too.
- The limits, said on the page: the interpreter is not the C backend. It runs the language and the standard library's pure parts, not threads, networking, the GUI,
@cImportortestblocks as the harness runs them, so chapters 2 to 15 run in the page and the others say "run this one in the terminal". Programs are exercise-sized; the interpreter's step budget bounds a runaway one. - Exit: every fill and fix exercise of chapters 2 to 15 grades in the page with the same verdict
nx topo checkgives, checked by a headless browser in CI against the wasm build of that commit.
1.5 added the real target, --target wasm32-wasi for programs; the C backend in the browser through a wasm C compiler, and the share URL, moved to 1.8's apps.
- Progress: the route map of the Topo with each pitch ticked as its exercises pass, kept in the browser (nothing to sign into) and by
nx topoin~/.nexium; a chapter shows what the next one needs. - More chapters, each a project people actually want to write: a log analyser (text processing at scale), a game on nexium-gui, an HTTP server with routing and JSON, a Discord bot (a token from the environment, the gateway over
std.websocket, a slash command that answers, an embed, running as a service; statusmith's Rich Presence from chapter 19 as the warm-up), SQLite through@cImport, a tiny language (lexer, parser, interpreter: the compiler in miniature), a program for the browser (wasm, 1.5), "port a Python script", and "profile and speed up" once the numbers page exists. Each chapter ends with common mistakes, the diagnostics a newcomer meets first.
The docs, more (from 1.3).
- A reference page per std module generated by
nx docfrom the doc comments, with every function's signature, effects and an example that the harness runs, replacing the one longdocs/std.md. - The error index: now a line of its own, errors you can fix alone, with
NXcodes and pages filled in from the reader's own code. - A cookbook: "how do I..." recipes (read a file line by line, parse JSON, talk to a socket, call a C library, ship to Python), each a complete program the tests run; a page of Nexium next to Python, Rust, Go and C for the same twenty tasks, for the reader arriving from one of them.
- Guides that the reference cannot be: effects in depth (how inference works, how to discharge
panics, what anunsafeblock promises), ownership without a borrow checker (the rules, the diagnostics, the patterns), performance (what allocates, what the C looks like,nx sizeandnx audit), and the ABI (1.7). - A style guide (what
nx fmtdecides and what it leaves to you), a glossary, an FAQ, and a "Nexium in five minutes" page for the reader who will not read the Topo. - The site: search (a client-side index built by
site/build.nx), a light/dark toggle beside the system setting, docs per release (/1.0/,/latest/), a printable Topo (one page, and a PDF and an EPUB built by CI), and "edit this page" on every page. - The site's API (
docs/api.md; done:latest,releases,std,topo,commandsas JSON under/api/, written at each deploy). To come: the search index as a document, the diagnostics index of the error pages, per-release documents (/api/1.2/), andnx doctorreadinglatest.jsoninstead of GitHub's API where the rate limit bit before.
std.tui (1.4).
- Raw mode, key and mouse events, cells, colours and layout: the base for the binary workbench,
nx topo, andnx dash, a project dashboard (tests, effects, size, leaks, live, keyboard-driven).
Visual tools (1.6).
nx size --gui: the binary as a treemap by function, click to zoom.nx leaks --gui: the unreleased objects with their allocation stacks.nx effects --gui: the call graph with effect-coloured nodes; click one for the provenancenx explainprints.- The ownership timeline: a run's moves, clones and drops on a scrubber, the source highlighted as it plays; chapter 8 of the Topo as an app.
- All built on nexium-gui: the library's own stress test.
nexium-gui grows up (1.8, Gangapurna).
- Anti-aliased TrueType text from a rasterizer in Nexium, Unicode, clipping and scrolling containers, tree, table, tabs and menus, multiple windows, the platform's text input (IME), high-DPI; X11, Wayland and Cocoa backends beside Win32, and a canvas one in the browser (moved from 1.5), the demo running on all of them.
nx gui gallery: every widget with its source beside it, clickable documentation.
nexium-web: a web front end, native (1.8). Today a Nexium program gets a web front end two ways. One is a Tauri app whose commands are thin wrappers over artifact rustlib exports: the export seam is stateless on purpose (no mutable globals behind an export, and no objects or slices handed back across it yet), so rich data crosses as JSON in a caller's []mut u8 and the state lives in Rust, or the program runs as a Tauri sidecar speaking JSON lines. The other is a localhost server on std.http's router with the page embedded by @embedFile, which owns no window and needs an Origin check and a per-launch token, since any page can reach localhost. This is the third way, Tauri's idea done in Nexium:
- A
nexium-weblibrary built like nexium-gui: a small C layer over the system's webview (WebView2 on Windows, WKWebView on macOS, WebKitGTK on Linux, the engines Tauri's wry wraps) that queues the page's calls the waygui/platform.cqueues input (decision 51). The C cannot be avoided: every webview's IPC hook is a callback,@cImportdoes not translate function-pointer typedefs, and C cannot be handed a Nexium function, so the shim owns the callback and the queue. - The shim pumps the system's event loop without blocking (
PeekMessage,g_main_context_iteration,nextEventMatchingMask) rather than calling arun()that never returns, sopump()works likebegin_frame(): no threads, and macOS keeps its UI on the main thread. A program keeps the shape ofgui/counter.nx, and the page's side isawait invoke("count"):
nexium var app = nexium_web.App.open(@cstr("notes"), 900, 600).? app.set_html(@embedFile("ui/index.html")) var notes = List(String).new() while app.pump() { // like begin_frame(): false once closed for call in app.calls() { // each JS invoke() since the last pump match call.name[..] { "add" => { notes.append(String.from(call.arg(0))); call.ok("null") }, "count" => call.ok(format("{}", .{notes.len})), _ => call.fail("unknown command"), } } }
- Where it can beat Tauri rather than copy it: Tauri limits what the page may reach with capability files it checks at run time. Here each handler the page can call carries effect bounds in its signature (
!blocks !nondeterministic !ffi), checked at compile time, andnx audit --checkfails CI the day a handler picks up I/O. - To prove first: the shim vendoring
webview/webview, which is C++ inside.artifact linkhands itsc_sourcestozig ccas they are, so a.ccfile withlibs = ["c++"]should build; that is the first thing to test, on all three platforms.
Apps (1.8).
- The Hut, a small native IDE in nexium-gui: an editor with the language server, run and test buttons, an output pane, the effects panel, the inspector. The GUI library proves itself the way the compiler did, by building the thing that builds with it.
- The notebook: the GUI REPL with cells, results and charts, saved as an
.nxscript with the outputs as comments, so a notebook is a program. - The playground, whole (moved from 1.5): the C backend in the browser through a wasm C compiler, so the page runs what the interpreter cannot, and share links: a snippet is a URL.
2.0 candidates: questions the spec review should settle#
Additions large enough to deserve a spec version of their own. Each is a decision entry first, an implementation second, and none is promised.
- Errors that carry data:
error Parse { Bad{ line: u32 } }with payloads incatch |e|andmatch, or the position that error sets stay names and context travels beside them. - Operator overloading through traits (
Add,Index,Eqis derived already) for numeric types written in Nexium, or the position that operators mean what they mean for builtins only. - Named arguments, or the position that an anonymous literal parameter (
f(.{ .width = 3 })) is the language's way. - Generic traits and associated types (
trait Container(T),trait Iterator { type Item }), which 1.1's iterator protocol may force. - Visibility levels (
pub(package)) and re-exports (pub import). - An
unsafeaudit:nx audit --unsafelisting everyunsafeblock, its reason comment (mandatory), and the foreign calls under it.
Ecosystem, in parallel with all of the above#
- The book: a chapter per spec section, each with a program; the tour is chapter one.
nx docrenders HTML for the book and for std, and the docs site is built by CI from the repository. nx new <template>: cli, service, library, gui, wasm.- Editors: done for Vim, Neovim, Helix, Zed, Emacs, Kate, Notepad++, nano and JetBrains (through LSP4IJ) beside VS Code and Sublime Text; a JetBrains plugin of its own, and Marketplace listings for Zed and Neovim, when the language server is semantic (1.3).
- The registry and
nx publish(decision 27's last item), seeded with the packages that leave the tree: the HTTP client, TLS, TOML, CSV, SQLite through@cImport, a CLI parser richer thanstd.args. - Governance: an RFC process for additions (the decision log becomes public proposals with a comment period), a release calendar, and the stability policy applied to
std(what a std module may change). - A second maintainer, on purpose:
good first issuelabels kept stocked, "the compiler in an afternoon" (a guided read ofself/in the order the architecture tour uses), every subsystem's owner named inCONTRIBUTING.md, and release rights shared before 1.4. A project with one maintainer is a project with a bus factor of one, and that is a weakness of the project, not of the language. - Third-party packages before the registry: a
packages.mdlist of packages people cannx addfrom git today, curated, so the ecosystem has a front door before it has infrastructure. The README's "In the wild" table is the other front door: the programs written in Nexium outside the tree, four today (statusmith's SDK; Point of Origin, where Nexium is the build language of an Odin and Unity game: the native build, the bindings generated from the Odin exports, the levels compiled to JSON; nxtls, the cryptography package and TLS 1.3 client; and nexium-discord, a Discord bot library on nxtls). A second kind of user, the one who writes their tooling in Nexium and their product in something else, is the one the@cImport,nx shipandstd.processwork serves, and the one to ask what is missing. - Translations of the book and the reference (docs/i18n exists for the README, language and architecture pages).
Always#
- Every release is verified on three platforms by CI before it is tagged, under the sanitizers, and by the fuzzer (1,500 iterations on every push today; a nightly long run, its findings filed, is the next step).
- The corpus grows at least as fast as the language: every release adds programs to
examples/,topo/code/andtests/specshaped like the programs people write (a JSON tool, a server, an editor, a ray tracer), because the tutorial's twenty-one programs found eight bugs the spec suite had not. - A bug reported from outside gets a patch release within a week of its fix;
KNOWN_ISSUES.mdlists what is open, with the workaround, so the reader never discovers a known bug the hard way. - Every language change names the spec constraint it serves and lands with an example or a compile-fail case.
- Every feature's errors are written before the feature, from the per-feature template (docs/diagnostics-template.md); a feature that cannot produce a clear error is simplified, not its message.
- Every open design call goes in
DECISIONS.mdthe day it is made. - The Nexium share of the repository rises every phase and is never padded.
Not planned#
- A garbage collector. Reference counting with
weakis the design. async/await. Threads, channels and blocking calls are the concurrency model (decision 82); an event loop can be a library.- A native backend. C is the backend; Zig or a system compiler is the toolchain, and the installers make that invisible.
- Async/await as colored functions, unless phase 2 finds a design that fits the effect system.
- Exceptions. Error unions and panics are the two failure modes, and panics never cross an export boundary.