On this page
[Unreleased] Added Changed Fixed [1.5.0] - 2026-10-04 Added Fixed [1.4.1] - 2026-09-26 Fixed [1.4.0] - 2026-09-25 Added Changed Fixed [1.3.2] - 2026-09-25 Fixed [1.3.1] - 2026-09-25 Fixed [1.3.0] - 2026-09-24 Changed Added Fixed [1.2.1] - 2026-09-22 Security Fixed Added [1.2.0] - 2026-09-21 Added Changed Fixed [1.1.0] - 2026-09-21 Added Fixed [1.0.3] - 2026-09-20 Added Changed Fixed [1.0.2] - 2026-09-20 Fixed Added Fixed [1.0.1] - 2026-09-20 Added Fixed [1.0.0] - 2026-09-20 Removed Changed Fixed [0.9.0] - 2026-09-20 Added Fixed [0.8.0] - 2026-09-20 Changed Added Fixed Removed [0.7.0] - 2026-09-19 Added Fixed [0.6.1] - 2026-09-19 Added Fixed [0.6.0] - 2026-09-19 Changed Added Fixed [0.5.0] - 2026-09-19 Added Fixed [0.4.0] - 2026-09-19 Added Fixed [0.3.0] - 2026-09-19 Added Fixed [0.2.1] - 2026-09-19 Added Fixed [0.2.0] - 2026-09-19 Added Changed Fixed [0.1.0] - 2026-09-18 AddedChangelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning. The file is validated in CI with patchnotes. The line under each version header is the release's name, a place on a mountain; docs/release-names.md has the scheme.
[Unreleased]#
Added#
- A new mark, and every asset drawn from it by one script (
scripts/make_assets.py, Pillow and nothing else): Fuji over a lake, the lake reflecting the mountain asnx, indigo on washi by day and the same scene by night under a dark sky.assets/logo.svgswitches itself underprefers-color-scheme: dark; the README's banner is a<picture>with a light and a dark file; the social preview, the icon at every size, the VS Code icon and the installer's wizard images are the same scene. The site and the Topo take the mark's palette, day and night.scripts/make_icon.pyandmake_wizard_images.pyare gone. m.get_or_put(key, default): the value ofkeyas a*mut V, put in withdefaultwhen the key is new, in one lookup (decision 125), socounts.get_or_put(word, 0).* += 1counts a word. When the key is there the entry keeps its own key and value, and the two passed in are dropped. The pointer is a view into the map, stale once the map changes (rule V3). Thewordsbenchmark, written this way, takes two thirds of the time it took withgetand thenputon Windows (2.0 times C's, from 3.0), and the same on Linux with gcc.- A fifth benchmark,
trees: 174,754 binary trees ofref classnodes (every second depth from 4 to 16, a stretch tree of depth 18 and a long-lived one of depth 17), built, counted and dropped, in the five languages (bench/trees.*, decision 127). Counting binds each child withlet, a copy retained and released: the pair the roadmap's ARC elision item is about, twice per inner node, listed bynx refcounts bench/trees.nx. On Windows with zig cc, Nexium runs it in 1.08 seconds against C's 0.85 (1.3 times, in both modes), Rust's 0.94 and Go's 0.50 with its collector; Python takes 13.
Changed#
- Building a short string costs less. An integer that fits in 64 bits is written with 64-bit arithmetic and a constant base of 10, where every digit was a 128-bit division (a library call); and a string's first block holds 16 bytes, where it held 4 and the next append grew it at once, so
format("w{}", .{k})allocates once instead of twice. Thewordsbenchmark (aformatand a count in a map per word) runs in 1.39 seconds where it took 2.26 on Windows with zig cc, and in 0.73 where it took 0.91 on Linux with gcc; C takes 0.48 on both. - The runtime keeps small freed blocks for reuse: up to 64 bytes and a little over, in four classes 16 bytes apart, up to 64 per class, on a list of the thread's own, handed out before
mallocis asked (decision 126). A block is allocated at its class's size, so a cached one fits any request of its class, and areallocinto or out of the classes makes a whole block; the classes end where the C library's own steps do, so a cached block costs no more than its request would; a thread the runtime started, and an exported call, return their blocks when they end. Under AddressSanitizer the cache is off, andnx leakscounts what it counted. Thewordsbenchmark runs in 0.66 seconds where it took 0.87 on Windows with zig cc (1.6 times C's, from 2.1), and the compiler emits its own C in 0.58 seconds where it took 0.70; on Linux with gcc, whose C library caches small blocks itself, nothing measurable changes.
Fixed#
- A compound assignment's target was checked twice, and the interpreter evaluated it twice:
slot(&mut xs, i).* += 1calledslottwice undernx playand at compile time (compiled code called it once), and a target that moves an owned value in,m.get_or_put(word, 0).* += 1, failed with "use ofwordafter it was moved". The target is evaluated once, as a place read and then written (SPEC 6.1). - A view bound with
letcounted as taken at theletrather than where its value was made, so a change its own initializer made first, as inlet v = { xs.append(2); xs[..] }, was reported as made after the view (rule V3). - The README and its translations, the Topo and the package pages still described 1.4. The README's status and highlights are 1.5's (WebAssembly, the 32-bit downloads, ARM, CI on five machines) and its line counts are counted again; chapter 15 of the Topo showed
dotcarryingpanics, which the compiler has proved away since 1.1; chapters 16, 18 and 23 promised as coming what 1.4 and 1.5 shipped or moved;docs/discord.mdanddocs/packages.mdnamed nexium-discord 0.1.0 and nxtls 0.1.0, where 0.2.0 runs on Windows and 0.5.0 speaks TLS 1.3. bootstrap\build.ps1failed at stage 2 since it was written: the compiler's emitted C went through a PowerShell pipeline, which splits the output into lines and joined them with nothing, so the file had no newlines and nomainto link. The C is written throughcmdnow, which keeps the bytes as they are.build.sh, which CI runs on Windows too, was never affected.
[1.5.0] - 2026-10-04#
Everest: Base Camp — 5,364 metres on the Khumbu glacier, where the climb to the next summit is staged: one source, many platforms. Programs build for WebAssembly (wasm32-wasi) and run under Node.js's WASI, a panic unwinding as it does natively, and artifact wasm ships a library as a module with a JavaScript loader for a page or Node.js; programs build for 32-bit x86, ARMv7 and RISC-V 32, which CI runs on its machines and under QEMU, the compiler builds itself as a 32-bit program, and every release carries nx for 32-bit Windows and Linux and for ARMv7 Linux, a Raspberry Pi's 32-bit OS among them; Linux and Windows on ARM are tier 1, every suite run on GitHub's arm64 machines. @sizeOf is confirmed by the C compiler on every target, and the test harness takes --target. The seed is regenerated from the final sources.
Added#
- CI checks the links between the repository's Markdown files (
scripts/check_links.py, in the lint job of CI and of CI (prose), so a change to Markdown alone is checked too): every relative link must name a file that exists, spelled with its case, and every#anchora heading of that page as GitHub makes its anchors, translations included. The translations' index had promised this check; it runs now. - 32-bit targets (decision 122):
nx build --target i686-linux-gnu(ori686-windows-gnu,i686-linux-musl) builds a 32-bit x86 program,i686read as zig'sx86with SSE2;armv7-linux-gnueabihf(zig'sarm),armv6-linux-gnueabihf(the Raspberry Pi 1's and Zero's CPU) andriscv32-linux-muslbuild too. CI runs the spec cases, the examples, the standard library's tests and the Topo's programs fori686on its runners and forarmv7andriscv32under QEMU. The checker computesisizeandusize, their literals' ranges,@sizeOfand compile-time arithmetic at the target's width, while the C stays the same for every target (usize's bounds areSIZE_MAXand its kin);i128is_BitInt(128)where C has no__int128. The compiler builds itself as a 32-bit program, and a 32-bitnxon a 64-bit machine builds 32-bit programs by default. @sizeOfis confirmed by the C compiler: each answer is a_Static_assertin the generated C, on every target.- The test harness takes
--target: every case it builds, from the spec, the examples, the standard library's tests, the Topo and the compile-fail cases, is built for that target and run. A case marked// NOT ON <arch>: <why>is left out for a target of that architecture, with a note saying so. - WebAssembly (decision 123):
nx build --target wasm32-wasibuilds a program as a.wasmmodule, andnx runandnx testrun it under Node.js's WASI, where it sees the working directory and a/tmpof its own beside the module. A panic reaches its boundary as it does natively (its message and line, exit code 101, a test runner going on to the next test) through WebAssembly's exception handling;runtime/nx_wasm_sjlj.cholds the three calls zig's WASI libc leaves out.@target()is("wasi", "wasm32", 32)there. CI runs the spec cases, the examples, the standard library's tests and the Topo's programs as modules, all but those that start processes or open sockets, which WASI (preview 1) has none of. - Linux and Windows on ARM are tier 1: CI's test job runs the seed, the fixed point and every suite on GitHub's arm64 runners (
ubuntu-24.04-arm,windows-11-arm), as on the three desktops. artifact wasm(decision 124):nx shipbuilds the library forwasm32-wasias a module with nomain, exporting itsexport(c)functions, and writes<name>.js, an ES module whoseload()instantiates it and returns its functions, with<name>.d.tsand apackage.json. It runs in a page and in Node.js 20 or later; slices are copied in (and back for[]mut), a failure throwsNexiumError, and a panic throwsNexiumPanicwith its message, after which the module goes on answering. The module is stripped of the C library's debug information, which was 95% of it:examples/ropesim.nx's is 39 KB. The harness runs that package under Node.js and gets the native package's answers, and CI loads it into a page in headless Chrome (tests/ship/wasm_page.html) and gets them again.- Every release carries three 32-bit downloads, tier 2 in
docs/platforms.md: a zip for 32-bit Windows (i686-pc-windows-msvc), and archives for 32-bit x86 Linux (i686-unknown-linux-musl) and ARMv7 Linux (armv7-unknown-linux-musleabihf, a Raspberry Pi 2 or later on a 32-bit OS), both static, so they run on any distribution. The install scripts pick them by the system rather than the CPU, so a 32-bit Raspberry Pi OS on a Pi 4 gets the ARMv7 build, and build from source on a CPU without SSE2 or NEON; Scoop's manifest and the AUR package list them.
Fixed#
- An empty path names nothing on every platform:
io.file_kind("")is 0, andio.read_file(""),fs.list("")and the rest fail as for a missing file. Under WASI they found the directory the program runs in, and on Windowsfs.list("")listed the root of the drive. @target()named the architecture of ariscv32buildunknown.- On Windows on ARM,
nx shipcompiled the Rust crate's static archive for x86-64, which an ARM64 cargo cannot link; it follows the machine now. @sizeOfof aStringor aListwas 24 on a 64-bit target where C lays them out in 32 bytes (their arena pointer was not counted), of aMap40 where it is 112, of an error value 8 where it is 4, and a pointer, slice or collection field aligned to 8 whatever the target;nx layout's offsets after such fields were off by as much.std.httprefused aContent-Lengthor chunk size past what the machine can hold (4 GB on a 32-bit one) asInvalidInput; it isTooLarge, as any size past the limit is.nx fmtwrote a complement before a parenthesis with a space,~ (0 as usize);~is tight before(now, as!and a unary-are.- The release notes named the ARM64 zip as the portable Windows build (1.4.1's did: it was the first name ending in
windows-msvc.zip); they name the x64 one. - The Spanish, Japanese and Chinese translations of the language reference and of the tour of the compiler (
docs/i18n/*/language.mdandarchitecture.md) are translated again from the current English. They dated from 0.1, patched once at 1.2: they described the compiler in Rust and lacked everything since, fromownparameters and benchmarks to the sockets, TLS, threads and child processes of the standard library. The Japanese and Chinese pages keep each paragraph on one line, since a line break between two characters shows in a browser as a space. docs/architecture.mdgave the runtime as 900 lines (it is about 4,200) and listed none of the platform layer added since: sockets, TLS, threads, child processes.docs/language.mdleftnet,threadandsyncout of the builtin namespaces.
[1.4.1] - 2026-09-26#
Annapurna: Ang Tharkay — the sirdar, who declined the summit and kept the camps supplied, for a patch to the supply line: when the manifest changed a dependency's tag, nx fetch went on building the old tag's code, and now it fetches the new one. The seed is regenerated from the final sources.
Fixed#
nx fetchresolves a dependency again when the manifest changes its tag or its git source. It held the dependency to the commit the lock had for the old tag, and with no lock kept a checkout of another tag innexium_modules/, so a tag bumped innexium.tomlbuilt the old code and wrote the new tag beside the old commit innexium.lock;nx updatewas the way out. Found bumping nexium-discord's nxtls from v0.4.0 to v0.5.0; the packages suite now changes a tag both ways.
[1.4.0] - 2026-09-25#
Annapurna: the Sanctuary — the basin ringed by the peaks, where the first mountain ends: a standard library people stop supplementing. Collections (std.sort, std.heap, std.set, std.deque), std.hash and random.secure, paths, the environment and config folders, UUIDs, logging, CSV, TOML and base64; Map hashed with SipHash under a key per process and kept in the order its keys came; time zones and ISO 8601 in std.time; an HTTP client with redirects, timeouts and streaming bodies, HTTPS over the platform's own TLS or a TLS layer such as nxtls, and WebSockets, on which nexium-discord runs a Discord bot; programs run alongside and talked to while they run, with signals and exit codes by name; select over channels, atomics, and threads that end before the call does; grapheme clusters and Unicode's case mapping; property tests that shrink what they find, on the driver the fuzzer uses. The seed is regenerated from the final sources.
Added#
xs.swap(i, j)on a mutable slice (and aList): the two elements exchanged, any element type, a panic on an index out of bounds. It is what the new collections move owned values with.std.sort:by(T, xs, less)(a heapsort: in place, O(n log n), not stable),stable_by(T, xs, less)andby_key(T, K, xs, key)(a merge sort of the positions, then each element moved into place along the permutation's cycles: no element copied, any type),is_sorted,is_sorted_by, and on ascending sliceslower_bound,upper_boundandbinary_search.std.heap:Heap(T), a priority queue by a comparison (heap.by(T, less)):push,pop(the least first),peek,len.std.set:Set(T)overMap(T, bool):add(true when new),contains,remove,items, andunion,intersection,difference,is_subset.std.hash:fnv1a64,siphash(key, data)(SipHash-2-4, for tables keyed by untrusted input) and SHA-256 (sha256,sha256_hex, andSha256for data that arrives in pieces), each checked against its reference vectors.std.deque:Deque(T), both ends in O(1) amortized (two Lists back to back):push_front,push_back,pop_front,pop_back,get,first,last.random.secure(buf): a[]mut u8filled from the operating system's secure generator, for keys, tokens and UUIDs, whererandom.intandrandom.floatare a fast generatorrandom.seedmakes repeatable. Windows asks BCryptGenRandom, Linux the getrandom system call (made directly, so programs still need nothing past glibc 2.17) or/dev/urandomon older kernels, macOS and the BSDsarc4random_buf, WASIgetentropy. It carries thenondeterministiceffect, is refused at compile time, and fails withIoErroronly where there is no generator (decision 119). It is whatstd.uuid, a SipHash-keyedMapand a TLS client on Windows were waiting for.std.path: paths as text, without the file system.join,join_all,parent,base_name,extension,stem,with_extensionandnormalize(whichstd.fshad, and whosestd.fsnames now call these);root,components,is_absolute,is_relative;starts_withandstrip_prefix, compared component by component (a/bcdoes not start witha/b);relative, the path from one directory to another (src/app,src/lib/x.nx->../lib/x.nx);to_slashandto_native.std.env: variables (get,get_or,set,unset,all),homeandexpand_home, and the directories each platform expects a program to keep its files in:config_dir,data_dir,cache_dirandstate_dir, from the XDG variables or~/.configand its kin on Linux and the BSDs,~/Libraryon macOS, andAPPDATAorLOCALAPPDATAon Windows..envfiles:parse_dotenv(comments,export, single and double quotes) andload_dotenv, which sets what is not set yet, or everything when asked.std.uuid: UUIDs as RFC 9562 has them.v4(random) andv7(the time first, so later ids sort after earlier ones), both fromrandom.secure;parse(the canonical form, the bare hex, braces orurn:uuid:),text,version,time_ms,nil,maxandfrom_bytes.std.log: leveled, structured logging. ALoggerwrites text lines (2026-09-25T18:04:05.120Z INFO listening port=8080) or JSON lines to stderr from its level up, with fields made bystr,int,floatandflag;withgives a logger whose every line carries some fields,parse_levelreads a level from configuration, andkeepingkeeps the lines for a test to read.std.csv: comma-separated values as RFC 4180 has them.parse,parse_with(any separator: tabs, semicolons),by_header(each row a map from the header's names), andfield,to_textandto_text_withfor writing. A stray quote or a quoted field never closed is an error, not a guess.std.toml: TOML 1.0.parsereads a document intoTomlvalues: every string, integer, float, date and time form, arrays, inline tables and arrays of tables, under TOML's rules that a key and a table are defined once;problemsays on which line a document breaks one and why.get,lookup(package.name),at,len,keys,as_str,as_int,as_float,as_boolandas_timeread it, andstringifywrites one.std.base64: base64 as RFC 4648 has it:encodeanddecodein the standard alphabet,encode_urlanddecode_urlin the URL-safe one without padding, as JSON Web Tokens carry it. Decoding is strict (a character outside the alphabet, misplaced padding or an impossible length is an error);std.bytes.unbase64stays the lenient reader.std.timehas time zones, from the platform's database:zone(name)(Europe/Berlin,UTC, a fixed offset such as+05:30, or a POSIX rule such asEST5EDT,M3.2.0,M11.1.0) andlocal_zone()($TZ, else the system's). Linux, macOS and the BSDs have the database as zoneinfo files, read as TZif with the rule at their end for the years past their last change ($TZDIRfirst); Windows has it in ICU, which the runtime loads at the first call. AZonegives the offset, the daylight flag and the abbreviation at any instant, breaks an instant down (at), turns a wall clock into an instant (instant: a skipped time is read with the offset before the change, a doubled one is the first) and formats with%Z(CEST). The two databases were compared: for 30 zones, every week from 1850 to 2150, they give the same offsets, except where Windows' copy of ICU is older than a change (Egypt's summer time since 2023).std.timereads and writes the rest of ISO 8601: week dates (2026-W38-6, withiso_weekandweeks_in_year) and ordinal dates (2026-262), the basic format (20260919T041514Z,iso_basic), times to the hour or the minute, a comma before the fraction, and offsets as+0200or+02;formathas%G %V %u(the ISO week),%A %B(full names),%I %p(the 12-hour clock),%yand%s.DateTimedoes calendar arithmetic (add_days,add_months, which keeps to the month's last day,add_years) and exact arithmetic (plus,minus,until).Durationcompares with==and<, and does arithmetic:times,div,ratio,neg,abs,truncateandroundto a unit, andwhole_secondstowhole_days.iso()writesPT1H30MandDuration.parse_isoreads it (weeks, days, hours, minutes, seconds; a fraction on the last).time.zone_rules(name) -> !string, the callstd.timegets a zone from on Windows: ICU's periods of the zone as text. Elsewhere it isNotFound.std.httphas a client worth keeping and HTTPS through a TLS slot (decision 120).http.client()makes a client forhttp://, andhttp.client_with(T, &mut layer)one whosehttps://goes over a TLS layer; a client holds the settings (timeout_msfor each connect and each wait,max_redirects,max_body, headers for every request).send,getandpostread the answer whole, andopengives aStreamingwhose body comes in pieces (next) as it arrives. The slot is the traithttp.Transport(connect with a deadline, send, recv, a timeout for the waits apart from the connect's, close, and whether the connection was cut rather than closed);Plainis TCP, and nxtls fills it in a few lines (the trait's documentation has them), checked against Discord's API, Google, example.com and GitHub, whose redirect fromhttp://tohttps://crosses from one transport to the other. A client calls its transports directly, not throughdyn, so a request has their effects and no others: a plain client's areallocates blocks panicsas before. Redirects: a 303, and a 301 or 302 to a POST, turn into a GET without the body; Authorization and Cookie are not sent to another host; a relativeLocationresolves against the URL, and one with another scheme is refused. Bodies: by Content-Length, by chunks (also after other codings,gzip, chunked), or until the end of the connection, which iserror.Truncatedwhen a TLS connection was cut without close_notify; interim1xxanswers are skipped and a HEAD has no body.std.websocket: a WebSocket client (RFC 6455) over std.http's transports,ws://over TCP (connect) andwss://over the TLS layer a program gives it (connect_with), or built up with headers of its own (socket,header,open).recvgives whole messages (fragments joined, text checked to be UTF-8,max_messagebytes at most), answers pings on the way, and reports the server's close or the end of the connection (1006);send_text,send_binary,ping, andclosewith the closing handshake. The first protocol mistake by the server ends the connection with 1002 and the reason inproblem. Keys and masks come fromrandom.secure. The pure parts (the handshake's text and check, frames, the decoder) come from a Discord app, tested byte by byte against RFC 6455's examples; checked against Discord's gateway over nxtls (its HELLO, a ping's pong, a clean close) and an echo server on loopback.- A Discord bot library, the second package outside this repository: nexium-discord. A
Botwhosenextreturns the gateway's events as an enum (heartbeats, resumes and reconnects inside), the REST calls a bot makes, messages, interactions and slash commands; TLS from nxtls in std.http's slot. docs/discord.md points at it. hash.sha1andhash.sha1_hex, for the protocols that still require SHA-1 (the WebSocket handshake), never to check or sign data.- A trait of an imported module is named with its module, as a type is:
impl http.Transport for Conn,dyn http.Transportandwhere T: http.Transport(SPEC 8.3), where the impl was a parse error ("expected a trait name beforefor"). - The compiler reads
nexium.tomlwithstd.toml, where it had a reader of its own for a subset: a manifest may use all of TOML, and one that is not TOML says on which line and why. - Nexium in a notebook:
docs/install.mdshows the three cells that run a program in Google Colab or Jupyter on the wheel (!pip install -q nexium-lang, a%%writefilecell holding the program,!nx run), tried in Colab; the README's Install section points at them. - Programs run alongside:
process.start(argv)(andstart_withwith aStartsaying where each stream goes: a pipe, this program's, nowhere, or stderr into stdout) gives aChildwhosestdinis written withwriteandwrite_lineand whosestdoutandstderrare read byread_line,readorread_allas the program writes.wait(which closes its input first),wait_for(ms),try_wait,kill,terminate,signal,finish(whatrungives, for a program already talked to), andset_timeoutfor every wait (0 waits for ever, as every timeout in std does). While a call waits for one thing, what arrives on the other pipes is kept, so a program never stalls on a full one. A missing program iserror.NotFoundeverywhere (glibc says EACCES when a directory on PATH cannot be searched; the runtime then looks for the file itself). Windows gets overlapped named pipes for it; Linux, macOS and the BSDs a poll loop. Statusand names for exit codes and signals:EXIT_USAGEand the rest of sysexits,EXIT_NOT_FOUND(127) andEXIT_CANNOT_RUN(126),exit_name, the signals numbered alike on Linux, macOS and the BSDs (SIGTERM,SIGKILL, ...) andsignal_name. On Windows, which has no signals,signalends the program with exit code 128 + the signal, and itsStatusstill says which.process.trap_signals(): SIGINT, SIGTERM and SIGHUP (on Windows Ctrl-C, Ctrl-Break, the console closing, logoff and shutdown) no longer end the program but are queued forprocess.caught()andwait_signal(ms), so a service finishes what it was doing. The builtins beneath (process.spawn, thechild_*calls,trap_signals,next_signal) are in docs/language.md.std.thread:select2(A, B, a, b, ms)waits until one of two channels (of any types) has a value or is closed and says which, andselect(T, chans, ms)does the same for any number of one type; null when the time runs out. A channel rings the bell of every select waiting on it, so a select sleeps until something happens.recv_for(ms)receives with a timeout (error.Timeout), andis_closedsays whether a channel is.thread.each(T, items, f)runsfon every item, each on a thread of its own, all at once, andthread.bothruns two functions of different argument types together; both return only when every thread they started has ended, so what the threads point into, the caller's locals among it, outlives them, and a panic in one is raised once all are done (thread.join_all). They are for threads that work together, a pipeline's stages over channels;for parallelstays the tool for splitting work over the cores.thread.Atomic: ani64threads read and change without a lock,load,store,addandsub(giving the value before),swapandcompare_swap, sequentially consistent. Beneath it,sync.atomic_load,atomic_store,atomic_add,atomic_swapandatomic_cas, and for select and timeoutssync.wait_for(a condition wait with a timeout) and bells (sync.bell_new,bell_ring,bell_wait,bell_free). The tests run clean under ThreadSanitizer as under ASan and UBSan.- Grapheme clusters in
std.text:grapheme_end(s, i),graphemes,grapheme_countandtruncate_graphemes, by Unicode's extended grapheme clusters (UAX #29, Unicode 16.0), Indic conjuncts (GB9c) and emoji ZWJ sequences included. Checked against ICU (Node'sIntl.Segmenter, Unicode 17) on 200,000 random strings drawn from every break class: all 887 differences are data Unicode 17 changed (U+11A3A no longer Prepend, some tiles no longer pictographs, more viramas joining conjuncts). text.scalars(s)gives the text'schars,text.foldandfold_charcase-fold (full and simple),pad_leftright-aligns in terminal columns,truncate_widthcuts to a width between grapheme clusters, andUNICODE_VERSIONnames the tables' version.- Property tests in
std.testing:check(T, gen, holds)asks a generator for 100 values (NX_CASESfor more,NX_SEEDfor another seed) and fails the test whenholdsis false of one;check_showwrites the failing value. Generators draw from atesting.Rng(int,size,pick,flip,float,bytes,ascii,text), which keeps every choice: a failing case is shrunk by making it again from fewer and lower choices while it still fails, so any generator shrinks without code of its own (a failing list of ints comes out as[0, 0], a bound as the bound itself).searchandreplayare the driver beneath, returning what they found; generators and properties may be closures. - HTTPS with no package:
http.get("https://...")and every client without a TLS layer of its own go overhttp.SystemTls, the platform's TLS, loaded when first used so no program links it: SChannel on Windows, Security.framework's Secure Transport on macOS, OpenSSL's libssl (3 or 1.1) on Linux and the BSDs. The server's certificate is checked against the system's roots and the host's name, andSystemTls.problem()says in words why a connection failed ("the server's certificate has expired"). Beneath it,net.tls_connect,tls_send,tls_recv,tls_truncated,tls_close,tls_availableandtls_problem. Checked on Windows and Linux against example.com and Google, and against badssl.com's expired, wrong-host and self-signed certificates, each refused with its reason;NX_ONLINE=1runs those checks in std.http's tests. A TLS layer given toclient_with(nxtls) still takes precedence.
Changed#
http.request,http.getandhttp.postgo through a plainhttp.client(): the same 15-second waits and five redirects, the new redirect rules, anderror.Unsupportedfor anhttps://URL (it waserror.InvalidInput), which needshttp.client_withand a TLS layer. The User-Agent saysnexium-http/0.4, andhttp.HeaderderivesClone.- The numbers are measured: the four benchmark programs run about a hundred times longer (
fib(42), 10,000,000 n-body steps, primes below 100,000,000, 10,000,000 words), about a second each in the compiled languages, where they finished in 5 to 20 milliseconds and timed mostly the start of a process. Nexium is timed insafemode (its checks on) as well asfast, and the page adds each language's time as a multiple of C's. The Bench job's check compares Nexium against C in the same run, so a slower runner no longer fails it (the v1.3.1 tag's run did, twice, with every language slower), and a baseline of other sizes is not compared.sieve.ccounts inlong long, aslongis 32 bits on Windows andi * ioverflowed there. - A
Mapiterates in the order its keys were first put (decision 121):for k in m,for (k, v) in m,keys()andvalues()give the keys in that order, whatever they hash to; a put of a key already there keeps its place, and a removed key leaves no gap. The order was the hash table's before, and not the onenx playand the REPL gave, so a program that printed a map's keys without sorting them prints them in a different order now. Keys are hashed with SipHash-1-3 under a key drawn once per process from the operating system's generator, where the hash was FNV-1a with no key, so no one who chooses a program's keys can make them collide (hash flooding).std.setiterates in the same order. - The README and its translations have a Speed section: the four programs' times in Nexium's two modes, C, Rust, Go and Python, from the Bench run of 2026-09-25, and what they come to; its links to the numbers page go to the site, which has the latest run.
- Release names move to the next mountain at the
.5of each line (decision 117): a major is still a summit, and the climb to it starts halfway through the line before (1.5.0 isEverest: Base Camp, 2.0.0Everest: Summit), the minors after a summit taking the mountain's other routes and the way down. Before, 2.0.0 would have stood on Everest's summit with its route still to climb. The rule, the plan and every mountain's range indocs/release-names.mdfollow. std.textmaps case and measures width from Unicode 16.0's tables (scripts/unicode_tables.pygenerates them from the character database as Perl compiles it, which Git for Windows ships):to_upperandto_loweruse the full mappings of every script (straßeisSTRASSE,fiisFI, and Armenian, Georgian, Cherokee, Deseret and the rest map, where fixed offsets reached Latin, Greek and Cyrillic only),to_lowerwrites a final sigma where a word ends,eq_ignore_casecompares by full case folding (StraßeequalsSTRASSE),upper_charandlower_charfollow the simple mappings, andwidthcounts grapheme clusters (an emoji sequence or a flag takes two columns) withis_zero_widthandis_widecovering every script. Every code point Python 3.13 (Unicode 15.1) knows maps as it does, but the two capitals Unicode 16 added (for ƛ and ɤ).- The compiler uses the library where it had its own:
nx upgradechecks a download's SHA-256 withstd.hash, where it ran whichever ofsha256sum,shasumandcertutilthe machine had (a machine with none could not upgrade), andnx doctorandnx upgraderead GitHub's answer withstd.jsonrather than by looking for the first"tag_name"in the text.examples/tool.nxcounts its levels in aMap, whose keys keep the order they came. This is 1.4's exit: those two programs and the compiler use the library where they wrote their own. http.client()andhttp.gettakehttps://over the system's TLS, where they wereerror.Unsupported;Unsupportedis left for a system with no TLS library (Linux without libssl) and WebAssembly.- The fuzzer (
tests/fuzz.nx) runs on std.testing's property driver: its cases come from atesting.Rngof the seed, and the first finding of each engine is shrunk (fewer edits, a shorter input) before it is saved. The hunter builds and runs one accepted mutant in N, chosen with the case, where it took every Nth, so a shrunk case is hunted the same way. - A started program inherits its three standard streams and nothing else: on Windows
process.run,process.execandprocess.starthand over only those handles (a handle list), and elsewhere their pipes close on exec, so a program started at the same moment on another thread no longer holds another one's pipes open (its reader waited for an end that came only when that other program exited).process.runon Windows passes this program's standard handles explicitly, so a child writes where this program's output was sent even when that handle was not inheritable.
Fixed#
nx fmtindented a block opened inside parentheses, a closure's body passed to a call or a struct literal as an argument, two levels, and its inner closing braces one: the body ofcheck(T, gen, |x| -> bool {came out ragged. Such a block is now one level in, like any other; three places in the compiler written the old way are respaced. Test: the fmt suite's block inside parentheses case.nx fmtspaced the pointer sigil after a slice or array type's brackets as a multiplication:chans: []*mut Channel(T)becamechans: [] * mut Channel(T), and[4]*u8likewise. A-,&or*after a]is now binary only when the brackets index a value (xs[i] * 2,[1, 2][i] * 3);][is tight in a type as in an index. Test: the fmt suite's slice of pointers case.- The tree-sitter grammar read four things the compiler accepts as errors:
derive(...)on an enum (CI failed onstd/toml.nx), a match arm after a braced one when it begins with.Variantor<<(it was read as{ ... }.Variant), qualified patterns (Shape.Dot,lexer.Kind.Ident), and bare effect words on a signature (-> !u32 allocates blocks). CI now also parsestests/specandtests/parse_smoke.nx. import std.timehid the builtintimenamespace:time.now()andtime.monotonic()were errors ("modulestd.timehas no functionnow") in a file that imported it. A std module named like a builtin namespace (time,process,net,thread) now adds to it: a call the module does not have goes to the builtin (SPEC 11).- A type of an imported module passed as a type argument,
thread.spawn(net.TcpListener, bool, serve, l), was taken for a value, and the call for one with too many arguments ("spawntakes 2 argument(s) but 4 were given");mod.Typeandmod.Type(A)are type arguments there, as they are in a type. process.exec(andstd.process.run_with) stalled on a program that wrote more than a pipe holds before reading its input: the input was written whole first, and the child, blocked on its output, never read it. The input now goes in as the child takes it while the output is read: a poll loop with the input among the pipes on Linux, macOS and the BSDs, a thread of its own on Windows. Three megabytes each way finish in 8 ms on Linux and 221 ms on Windows (PowerShell's start), where they waited forever.return println(...)in a function returning!voidcompiled to C that did not: the call's value, which there is none of, was taken for the result (another of the fuzzer's mutants). A call that returns nothing is made, then the function returns (SPEC 6 says so now).- An
artifactblock took any key and any kind without a word: a key spelled wrong was ignored, soartifact cli { sHack = "512M" }(the fuzzer's mutant of a spec case) leftmainon the default stack, and the recursion the stack was declared for overflowed it. An unknown key or kind is an error now, naming the ones there are. nx fmtjoined an enum literal to the operator or keyword before it,return.Blueandx ==.Red, reading the.as member access; one that begins a value is spaced as a word now, and member access anderror.Namestay tight.nx fmtjoined a function's brace to a return type that ends in a trait object,-> !dyn Transport{, takingTransport {for a struct literal;dynandweakare passed over, asmutwas, on the way back to the arrow.nx fmtwrote a unary minus before a parenthesis with a space,return - (v as i64), for the same reason as! (below. A unary minus is tight before(now; after an operand it still subtracts,a - (b).nx fmtwrote a negated parenthesis with a space,if ! (a or b): the rule that keeps a call's parenthesis tight let!through as if it began a spaced expression.!is tight before(now, as before a word, and the sources the old rule had spaced are respaced.
[1.3.2] - 2026-09-25#
Annapurna: Oudot — the expedition's doctor, and a patch that keeps a program alive. On Linux and macOS, a send to a connection the other side had reset, or input written to a program that had stopped reading it, raised SIGPIPE and ended the whole program before it could see the error; and a connect that failed at once came back looking open, so its first send did the same. Both are errors a program can handle now, each with a regression test, and the seed is regenerated from the final sources.
Fixed#
- A TCP connection that failed at once came back from
net.connectwith a timeout (andTcpStream.connect_timeout) as if it were open, and the first send then ended the program with SIGPIPE. A non-blockingconnectwith no route to the address (an IPv6 address on a machine without IPv6, a broadcast address) reports that at once, after which the socket selects as writable with nothing in SO_ERROR; the runtime waited and took it for connected. It iserror.IoErrornow. Found connecting nxtls to an IPv6 address under WSL. - A send to a connection the peer had reset ended the program with SIGPIPE on Linux and macOS instead of returning
error.IoError; sends pass MSG_NOSIGNAL now, and macOS sockets set SO_NOSIGPIPE. The same held forprocess.run_withwriting stdin to a program that ends without reading it all (grep -q, say): the caller died, and now it carries on and gets the child's exit code.
[1.3.1] - 2026-09-25#
Annapurna: Couzy — the engineer of the team, and a patch of the engineer's kind: the C the compiler writes, the runtime it links, the toolchain's messages and the release build. A branch's value could be a view of a String its branch had already released, a use-after-free in code without unsafe that the view rules promise cannot happen; an empty String handed its null pointer to memcpy and memcmp; a syntax error named a byte offset, and the REPL pointed into a program the user never sees; and the Linux build needed a newer glibc than its wheel says. Each is fixed with a regression test, and the seed is regenerated from the final sources.
Fixed#
- Syntax errors are reported the way type errors are: the message, then
--> file:line:col, the line and a caret, where they printedfile: error at 33: messagewith a byte offset into the file. A mistake the lexer and the parser both report at one position (a string cut by a newline) is reported once rather than three times. - The REPL points at what was typed. A syntax error's caret is under the input rather than at an offset in the program the REPL builds around it, an input that stops in the middle of a statement says so (
let x = 1 +blamed that program's ownreturn), and a diagnostic at the prompt shows the line and the caret without a--> <repl>:14:15numbered in that program; a file:loadreads is named with its own line numbers.nx upgradetyped at the prompt is answered as a command for the terminal (it was a parse error), and the banner that names a newer release says to leave and run it there. - An empty
Stringno longer reachesmemcpyormemcmpwith its null pointer, which is undefined behaviour even for a length of 0 and traps in a build against glibc:printlnof one,starts_withone, and one handed to the runtime as a path (io.read_file,io.file_kind, the other file calls), an environment variable's name or value, a process argument or a network host. The runtime copies a slice's bytes throughnx_bytes_copy, which skips an empty copy. Found by nxtls and a Discord app; a spec case exercises each and runs under CI's sanitizers. - A view of a String made in a branch no longer outlives it.
show(if c { format(...) } else { ... })withfn show(s: []u8)read freed memory, with no diagnostic: the String was released when its branch closed, before the call. The same held for amatcharm, a typed binding (let t: []u8 = if ...), a nestedif, a block's value or itsbreak :labelvalue, anif letcapture and an arm's binding. The storage a branch's value is made of now belongs to the whole expression and is released with the block the expression is in, as a plain expression's temporaries are (decision 118). Found by a Discord app's tests against glibc; a spec case runs each form under CI's sanitizers. - The x86_64 Linux build runs on every glibc from 2.17, as its wheel's
manylinux_2_17tag and the.debpromise. 1.3.0's needed 2.34, the release runner's own, sopip install nexium-langgave Ubuntu 20.04, Debian 11 and Amazon Linux 2 annxthat stopped with "GLIBC_2.34 not found". The release now builds both Linux architectures for glibc 2.17 and fails when one needs a newer symbol.
[1.3.0] - 2026-09-24#
Annapurna: North Face — the face of the first ascent, the original line followed through: the toolchain grown up. nx fix makes the checker's own edits, nx bench measures bench blocks, nx debug stops gdb or lldb at .nx lines with formatters for the language's values, --sanitize brings AddressSanitizer and UBSan to any build, if comptime builds only the branch the target picks, the language server answers from the checker when the program checks, and a large program's debug build is a C file per module, compiled again only where it changed. The view rules are errors, trait impls are held to their traits, and the checker is split by responsibility. The seed is regenerated from the final sources.
Changed#
- A program that uses
std.threadorfor parallelbuilds forwasm32-wasi, which has no threads: starting one fails, so its work runs in place, as it already did wherever a thread could not be started, and a lock has no one else to wait for. (The playground's compiler needs it: its driver compiles C on threads, which in the page it never does.) - The view rules V1 to V5 (specification 5.6 and 5.7) are errors, as 1.2 said they would be: a warning in one release, an error in the next (docs/stability.md). No program in this repository had one left.
--strictandNX_STRICT=1stay and make any warning an error; nothing warns now, and a deprecation will. The compile-fail cases that needed--strictfail without it. - The view rules' advice names a fix that works. "Clone the view" and "clone it" could not: a view's clone is another view of the same storage. The errors say to take the view later, keep an owned value (a
String, aList;.clone()of the value itself), moves.clone()instead ofs, or return an owned value, as the case is; the specification says the same (5.6). - The rules for methods are written down (decision 110): a method is visible wherever its type is, as a field is, and
pubon it marks the documented interface; a type'simplblocks may sit in several modules. The compiler behaved so already. nx doclists a public struct's or enum'spubmethods under it, from everyimplblock of the type in any module, each with its signature, inferred effects and doc comment; for a generic type, the methods of an impl of one instance (impl Pair(i32)) are headed by it. Methods were missing from the page before, doc comments and all.- The checker is no longer one file of 15,000 lines.
self/check.nxkeeps its state and core (types, definitions, loading, instances, the passes over the whole program, ownership, unification, ranges; 3,700 lines), and the rest of its methods sit in ten modules, each animpl Checkerblock:check_stmts,check_views,check_exprs,check_fields,check_calls,check_match,check_builtins,check_closures,check_printandcheck_interp. The code moved unchanged: the compiler before and after emits the same C and the same typed IR for every example, spec case, compile-fail case and itself.
Added#
- The package catalogue (
docs/packages.md) lists nxtls, cryptography in pure Nexium, and "In the wild" (the README, the site) lists it. It passes its tests with the compiler on main. nx fixmakes the edits the checker offers for its errors (decision 111): rule V4's.clone()where a value moves out from under a view of it, rule V5's@escape(...)around a value kept past itsusing arenablock, and_ =in front of a value nothing uses (not an error union, and not inside anifwith noelsewhose value is wanted). Each edit is tried on the program in memory, in whichever of the program's own files it falls, and kept only when the checker then reports fewer errors and none it did not report before;--checkreports without writing. Wrapping and saturating arithmetic are not edits it makes: which one a program means is the programmer's choice (decision 113). An error with an edit on offer ends with "note:nx fix FILEcan try an edit for N of these". The loader takes stand-in texts for any of the program's files (load_with_texts).- Incremental builds (decision 116): the debug build of a large program (about 200 KB of source or more;
NX_UNITS=1or0decides for any) is a C file per module, each object kept under<out-dir>/<name>.units/by a hash of its C and its command line, so a build compiles again only the modules whose C changed, on up to eight threads, and links;nx buildsays how many it compiled (1 of 36 C files compiled). A one-line change to the compiler rebuilds it in 0.99 seconds on Linux (gcc) and 1.75 on Windows (zig cc), where its one C file took 7.8. The runtime's state is shared between the files (NX_STATE), one copy defined by the root module's. - The language server answers from the checker when the program checks (decision 115): go to definition follows what each local, function, method and field resolved to, so
s.scale(2)goes to thescaleofs's own type; hover shows a local's or field's type (var total: i64) or a function's signature and effects; rename edits the declaration and every use in each of the program's own files. Where the program does not check the syntactic answers stand (self/lsp_index.nx). - Conditional compilation (decision 114):
if comptime C { } else { }evaluates the whole condition while checking and checks and builds only the branch it picks, so the other may name functions and externs that exist only on another platform (if comptime @target().0 == "windows").@target()is a value of the compile-time interpreter:--target's triple when cross-compiling, else the machine compiling, in the runtime's words.else if comptimechains, and a value's type is the taken branch's. #linedirectives in the C of a debug or sanitized build, so a debugger steps through.nxlines, a sanitizer's report names them, andbreak file.nx:12works. Each function is followed by a directive giving the generated code after it back to the C file's own lines;nx emit-c, whose C is the seed's, has none.nx debug FILE [-- args]: the program built for debugging and run under gdb, or lldb on macOS (NX_DEBUGGERnames another; a name withlldbin it takes lldb's flags), with formatters written beside the binary (runtime/nx_gdb.py,runtime/nx_lldb.py): aStringor[]u8as text, aList, slice or array as its elements, an optional as its value ornull, an error union as its value or the error's name, a struct's fields by their names, and aMapas its entries, a debug build naming each map type and keeping its key and value types for them. The CI jobdebuggerstops gdb and lldb at a.nxline oftests/debug/values.nxand checks each value they show.nx benchandbench "name" { ... }blocks beside the tests. The file is built optimized (safeunless--modesays otherwise) and each block measured: the iterations calibrated to a 10 ms sample, which is the warmup, then 21 samples, the median time per iteration printed with the fastest and slowest (bench sum to 1000 187 ns/iter (min 186 ns, max 193 ns; 21 samples of 64398)). The value of the block's last expression is kept (nx_bench_keep), so the work that makes it is not optimized away; an error union must betry'd. A panic or an error fails the benchmark and the others run on; a filter narrows the run and--quicktakes 1 ms samples.nx testleaves benchmarks tonx bench.benchis a keyword only before a string, so it stays a name elsewhere; the tree-sitter grammar and the editor files know it.--sanitize address,undefinedfornx build,runandtest: the C compiler's AddressSanitizer and UBSan, with debug information, the first finding stopping the program. Infastmode they put back the overflow and bounds checks the compiler dropped.zig cchas no AddressSanitizer runtime, soaddressasks for gcc or clang (NX_CC) and says so before anything is built. zig's UBSan reports only in a build not optimised for speed (at-O2a finding is a bare trap, the program dying without a word), so through zig a sanitizedsafeorfastbuild is compiled at-Og, and it checks a fixed array's bounds too (-fstrict-flex-arrays=3: the array is the last member of its struct, which C otherwise takes for a flexible one). The CI jobsanitizersruns every std module's tests withnx test --sanitize address,undefined.- The playground: the Topo's exercises and every example on the site run in the page. The compiler is built as WebAssembly (
site/play_build.sh: the C of the current sources forwasm32-wasi, 2.8 MB, under 1 MB compressed) and fetched on a reader's first Run;site/play.jsruns it in a worker with its own WASI layer, so nothing is sent anywhere and a long program never freezes the page. An exercise's code is editable, with Run, Check (graded against the recorded output, asnx topo checkgrades it, ticking the exercise's box) and Reset; a prediction takes the reader's guess before showing the output; every other code block with amaingets Run. The runtime compiles asNX_WASMon WASI: no processes, sockets, terminal orsetjmp, each failing as the operating system would refuse. nx play [FILE]: a whole program, from the file or from stdin, checked asnx checkchecks it and run frommainby the interpreter, nothing compiled. It is the playground's command and works the same in a terminal; the exit code ismain's own, 1 after diagnostics or an error frommain, 2 after a panic or a program the interpreter cannot run, which it says at the line of the program that led there.- The interpreter runs closures (captures by copy and by reference), trait objects (
dyn), binary construction (<<...>> into buf),@bitCast, ordering comparisons of byte slices, and slice builtins onxs[..](sort,fill,reverse). The Topo's chapters 2 to 15 run in the page. - The rest of the Topo course: sixteen new exercises for chapters 10, 11, 17 to 21 and 23 (forty-one in all, from a calculator's
%to a network handler tested without a network, a framebuffer, a package tag's version, an export through a caller's buffer and a neuron's gradient), and quizzes for chapters 10, 11, 17 and 19 to 22 (eleven in all). - Quiz questions the compiler grades (decision 109): a question can carry its program and ask about it (
ask: effects NAME,ask: output,ask: check), and a wrong answer is met with the compiler's own words (says:, fromnx explainand the diagnostics).nx topo verifyasks the compiler every such question and fails when a quiz has fallen behind; the harness runs it. On the site a click on a choice is graded at once and ticks the quiz's box when every question is right. - The
playsuite of the harness runs every spec case, example and Topo program throughnx playand holds it to the compiled output, and runs every fill and fix exercise of chapters 2 to 15; the CI jobplaygroundruns them through the WebAssembly build and the page's script.
Fixed#
- A socket's receive timeout never reached its reader:
TcpStream.reader()read with no timeout whateverset_timeoutsaid, so a client that connected and sent nothing held astd.httpserver (whoseserve_onesets ten seconds) forever. The reader keeps the stream's timeout (stream.Reader.from_socket_timeout) and a read past it iserror.Timeout. std.httpsized its buffers from what the peer declared: a request announcing a hugeContent-Lengthmade the server allocate it at once and die out of memory before a byte of body arrived, a chunk size of 17 or more hex digits overflowed and panicked, and a line that never ended grew without bound. A request body pasthttp.MAX_BODY(16 MiB;read_request_maxtakes another) iserror.TooLarge, answered 413 byserve_one; request, header and chunk-size lines past 64 KiB are too (stream.Reader.read_line_max); a chunk size pastusizeiserror.InvalidInput; and a body buffer grows as its bytes arrive. Found reviewing a server built onstd.http.docs/std.mdshowed only the last line of a multi-line doc comment (join: "replacesdir.");scripts/std_docs.pytakes the whole one.nx playwrote a float in exponent form as Rust does (1e-7,1e21) where the compiled program writes C's (1e-07,1e+21), and computedf32arithmetic inf64precision; a compiled program wrote anf32as thef64it widens to (0.1as0.10000000149011612). Anf32is now written as the shortest text that reads back as thatf32(0.3), and the interpreter rounds everyf32result tof32and writes floats with the compiled writer itself (specs6_float_format,s4_f32; the play suite compares both).- Rule R1 rejected a slice into a loop item's buffer over the caller's storage:
for s in xs { return s[..] }withxs: []Stringa parameter,return s.titleinfor s in self.sectionsin a method, anif letover a field, amatchbinding over a borrowed parameter, nested loops. A binding that copies an element is now judged by where the element lives (its origins, traced through pointers and slices), so those are accepted, while&s, a slice of an array field ofs, and a loop over the function's ownListor anownparameter stay errors (a map'sfor (k, v)walks copies it frees, so a view ofvstays one too). R1 also judged only one branch of a returnedifand no arm of amatch:return match n { 0 => a[..], _ => "x" }withaa local array returned a dangling slice, and so did anelsewhosethenwas safe; every branch is judged now (specs5_views_through_bindings, compile-failview_into_loop_copy,view_into_binding_array,view_through_local_list,view_through_own_param,view_of_map_entry,view_in_match_arm,view_in_else_branch). Found while writing a Discord app. nx fmttook a function's body brace after a return type that is a slice, an array or a*mutof a named type (-> []T {,-> *mut Json {) for a struct literal and wrote-> []T{;std.lists,std.json,std.thread, an example and two compiler files carried it. It looks back over[],[N]andmutto the arrow now (fmt suite case).- A
u128pasti128's maximum was written as a negative number by every formatting path ({},{x}, widths,format,to_string(), a?u128), and0 -% 1printed -1: the runtime wrote every integer as signed. Unsigned values are written bynx_w_uintnow. The literals behind them failed too: 2^128-1 was read as "literal-1", and 2^127 became 0 with no error, for ani128as well; a literal only au128holds is read in full, one no type holds is an error that says so, and a literal pasti128's range is an error fori128.parse_intinto an unsigned type readsu128's whole range (nx_parse_uint). The interpreter holds integers asi128:nx playrefuses au128past its maximum instead of panicking with a false overflow, and itsparse_intnow fails as the compiled one does (Overflowpast the target's range, where it returned300foru8). Specs4_u128,s11_parse_int_ranges, compile-failliteral_past_i128andliteral_pattern_past_u64. Found while writing a Discord app. - An integer literal under
aswas typedi64first and then cast:0xffffffffffffffff as i128gave -1,(0xffffffffffffffff as u128) * (0xffffffffffffffff as u128)panicked with an overflow, and0x1ffffffffffffffff as u128failed in the C compiler. The operand ofasnow gives a literal (or-on one) its numeric target as its type (specification 4), so those are the literal's values, and300 as u8is a compile error aslet z: u8 = 300is; it always panicked. A literal that does not fit the type it finally has is an error, thei64it defaults to included:let x = 0xffffffffffffffffprinted -1, and says now how to give it a type.-9223372036854775808with no type given is one literal, where it panicked at run time (specs4_literal_types, compile-failliteral_default_i64,literal_cast_out_of_range,literal_cast_wider_than_target). Found while writing a Discord app. - A function over
dyn Traitdid not build when nothing in the program coerced a value to that trait (fn g(d: dyn Area) -> i64 { return d.area() }): the vtable type took its slots' types from a vtable instance and, with none, declared every slotvoid (*)(nx_ctx*, void*), so the C compiler refused the call. The slots now come from the trait's declared signatures (vt_slot), and a trait impl's methods must match that declaration (decision 112, specification 8.3): the receiver, each parameter's type andown, the return type,Selfread as the implementing type; an impl returningi32for a trait'si64was accepted and called through the trait with the wrong type. The errors name the method, what it has and what the trait declares (compile-failtrait_impl_signature). Found while writing a Discord app. - A call through
dyn Traitignoredownparameters:d.put(s)andd.put(String.from("x"))handed the value to the callee and still dropped it in the caller, a double free (0xC0000374 on Windows, no message);swas not marked moved, so reading it after the call compiled; and aref classargument was not retained. The call now takes ownership as a direct call does (moves a local or a.?payload, passes a temporary without dropping it, retains a reference), and readingsafterwards is "use ofsafter it was moved" (specs8_dyn_own, compile-faildyn_own_moves). Found while writing a Discord app. - Moving an owning payload out of an optional or error union straight into an
ownparameter (take(x.?),take(x orelse d),take(try r), a method's or a generic function'sownparameter too) handed the payload over but left the source set, so its scope-end drop freed it again: a double free, which on Windows ended the program with heap corruption and no message. A call now moves those forms as it moves a bare local, zeroing the source, aslet s = x.?already did (examples/optional_move.nxgains the call forms;nx leaksfinds nothing). Found while writing a Discord app. panic(..)standing for a value whose C type is a struct (a struct, an enum, a slice,String,List,?T,!T, a tuple), as a tail, inreturn, in a typedletor as amatcharm, passednx checkand failed in the C compiler (nx_Kind _t1 = 0;), and a call to a-> neverfunction in value position failed for every type, scalars too (its C function returnsvoid). Both now stand for a zero of the expression's type after the call, asunreachablealready did, and a call through afn(..) -> nevervalue does the same (specs10_panic_as_value, ands10_never_call_panics, which takes the panicking path). Found while writing a Discord app.- A
whilecondition that made an owned temporary (while short(format("n{}", .{k})),while format(...).len < 4, aListreturned by a call) passednx checkand failed in the C compiler: the temporary's release was emitted after the loop, out of its C scope. The condition now has a scope of its own on every pass, released before the test, so acontinue, abreakand the last pass each free it once (specs6_while_condition_temporaries, also run undernx leaks). Found while writing a Discord app. - A method defined twice for one type, in two
implblocks of a module or in two modules, was accepted, and every call reached the first; it is an error at the second definition (compile-fail casesduplicate_method,duplicate_method_generic). A genericimpl(T) Pair(T)conflicts with every instance ofPair;impl Pair(i32)andimpl Pair(f64)may both define a method. - A struct reached only through pointers, such as the
selfof a trait's methods in a library with nomain, was declared in the generated C but never defined, and the C compiler refused it as an incomplete type. The fuzzer found it; the ship suite now builds such a library. nx explain FILE f effecton a function without the effect printedfn f(...) does not blocks; it prints the absence as a signature bounds it,fn f(...) !blocks.- The interpreter (the REPL,
nx -e, and nownx play) printed some programs differently from their compiled selves; running every recorded program through it found them all. Format specs were ignored ({:.2},{x}, widths); an enum printed asvariant 0and an error as?instead of their names; writes through a[]mut Targument or a local[]mutview of an array were lost;errdefernever ran;lines()kept an empty last line after a final newline;@refCountanswered 1; and aref classvalue was copied rather than shared, so an alias did not see a write through another. Each is fixed (spec casess6_format_widths,s6_mut_slices,s5_ref_class_shared), and the REPL keeps itsref classobjects shared across lines. A[]u8taken from a list of bytes printed as?; it prints as text, as compiled code writes it. nx checkaccepted anexport(c)function thatnx buildthen refused: which types may cross the C boundary (scalars, slices of scalars as parameters,layout(c)structs of scalars, no returned slice) was decided only when C was generated, so the editor andnx playsaid nothing. The checker decides it now, with the same words, and also refuses a parameter namedp_lenbeside a slicep, which C would see twice (compile-fail casesexport_returns_slice,export_list,export_struct_layout,export_len_name).- An exported function with a parameter named
out,ctx,rorprevdid not compile: the C wrapper named its own things so. They are_nx_-prefixed now, and the result pointer of a status-returning export isoutunless a parameter has the name, thenresult(spec cases15_export_names; the header says the same). nx fmttook.*and.?for openers and wrote nothing after them:i.*+= 1,if n.*== 3,match p.*{,{ o.?}. They are postfix operators, so what follows gets the space it gets after any operand (i.* += 1,match p.* {), and member access, indexing and calls stay tight (p.*.x,xs.*[0]). And a*before(was always taken for a pointer sigil, soa * (b - c)came out asa *(b - c); after an operand it multiplies and keeps its spaces. The tree is reformatted.- A format width did not pad a
String, a boolean, or an enum or error name in a compiled program ({>8}padded numbers and[]u8only); it pads every value written as text now.
[1.2.1] - 2026-09-22#
Annapurna: de Noyelle — the expedition's liaison officer, whose whole job was permits and diplomacy: this patch is the roads in. pip install nexium-lang and npm install nexium-lang are real, the extension is on Open VSX, the package is on Chocolatey and submitted to winget, every release attaches Debian and RPM packages and signed provenance for each asset, a Nix flake and a dev container open the repository anywhere, nx completions and nx man come from the compiler, the site has an API, and the Topo is a course of twenty-five exercises graded by the compiler. The workflows are pinned to commits and their tokens are read-only outside the publishing jobs. The seed is regenerated from the final sources.
Security#
- Every GitHub Action the workflows use is pinned to a commit, with its version in a comment that Dependabot keeps current, and the Docker base images to a digest, so a moved tag cannot change what a workflow runs. Each workflow's token is read-only except in the one job that publishes (the release, the images, the numbers branch, the CLA signatures).
install.shchecks a downloaded Zig against checksums it carries rather than ones fetched beside the archive. The VS Code extension's packaging tool moved to@vscode/vsce3.9, whosemarkdown-itandlinkify-itcarry the fixes for their quadratic-time advisories (a build-time dependency; nothing shipped was affected).
Fixed#
- The Release workflow pushed to Chocolatey from inside the Windows build and published the extension from the release job, so a registry's refusal failed the build and no release was made (Chocolatey returns 403 for every version after a package's first until that one is approved). The registries are their own workflows now, run when a release finishes:
chocolatey.ymlandextension.ymlbesidepypi.ymlandnpm.yml, each failing alone and rerunnable by hand for a tag. - Rule V3 reported a view into one field of a struct as stale when another field of the same struct grew (
for row in lv.rows { lv.origins.append(..) }), found in Point of Origin's level compiler. An origin remembers the first field on its path and a change the field it touched; they meet only when one is the whole value or both are the same field (spec cases5_views_by_field).
Added#
nx completions bash|zsh|fish|powershellandnx man, generated from one table of the commands and the options. The release archives carrynx.1andcompletions/, the install script and the Homebrew formula put them in place, the Debian, RPM and Nix packages install them.- Debian and RPM packages (
nexium_<version>_<arch>.deb,.rpm, amd64 and arm64) attached to every release, built by nfpm and installed in the release job as their test; a Nix flake (nix run github:Londopy/nexium) built from the one C file, checked in CI; a dev container on the Docker image, so the repository opens in a Codespace withnxready; mise through itsubibackend, documented. - The site's API:
api/latest.json(the current release and every asset's URL),api/releases.json,api/std.json(every module and public function with its doc),api/topo.json,api/commands.jsonandapi/index.json, written by the site builder at each deploy (docs/api.md). - A Pygments lexer for Nexium inside the wheel (
pip install nexium-langmakesnexiuma language Pygments knows:pygmentize -l nexium, Sphinx, MkDocs, Jupyter); the AUR packagenexium-bin(installers/aur/, written at each release); a tldr page (docs/tldr/nx.md, submitted to tldr-pages). - Signed provenance for every release asset (
gh attestation verify <file> --owner Londopy), the OpenSSF Scorecard workflow and badge, the site'ssecurity.txt,robots.txtandsitemap.xml,CITATION.cff, and.pre-commit-hooks.yamlwithnx fmtandnx checkhooks. pip install nexium-langis real: the wheels every release builds reach PyPI through trusted publishing (.github/workflows/pypi.yml, no token), when the Release workflow finishes or by hand for a tag. The npm packages have the same workflow (npm.yml, with provenance), a token for a package's first publish and npm's trusted publishing after. The VS Code extension is on Open VSX and the Chocolatey package in its moderation queue (extension.yml,chocolatey.ymlpublish a release's by hand), andLondopy.Nexiumis submitted to winget. The install doc's "Where to get it" table links every registry; the README carries their badges.- The Topo is a course: twenty-five exercises of five kinds (fill in the blank, fix the error, write it, make the tests pass, predict the output) and four quizzes at the end of chapters 2 to 16, graded by the compiler.
nx toporuns it in the terminal (the route map with progress,nx topo <chapter>writing the exercises to work on,check,hint,solution,quiz,reset; progress kept beside the update cache, or whereNX_TOPO_PROGRESSsays), and the site shows the same exercises with the solutions folded away, a "done" box per exercise kept in the browser and the count on every chapter link. The harness runs every solution, every starter's failure and a whole chapter throughnx topo.nx fmtspaces the hole of an exercise (???) like a word, so formatting a starter in the editor leaves it as written.
[1.2.0] - 2026-09-21#
Annapurna: South Face — the great wall, climbed by siege in 1970: memory safety without a garbage collector. The checker knows where every view points and enforces five rules over it: a view may not be returned inside a value or into an own parameter, stored past the storage it points into, read after its container grew or its value moved, and a value made inside using arena may not leave the block; @escape(v) is the copy that may. The rules are warnings in this release and errors in 1.3, and --strict makes them errors now. They found three bugs in the compiler on their first run (a struct literal evaluating out of written order, an arena leaked by an early return, two views held by luck), all fixed. The fuzzer runs mutants in debug mode, nx says when a newer release exists, and the Windows installer's welcome page carries the topographic contours. The seed is regenerated from the final sources.
Added#
- The view rules of 1.2 (
SPEC.md5.6 and 5.7): a view may not be stored past the storage it points into (V2), read after its container grew or was cleared (V3) or after its value moved away (V4), or returned inside a value (V1, extended from rule R1 to structs, closures and lists that hold views, and toownparameters), and a value made insideusing arenamay not be kept past the block (V5). The checker reports at the use that would read released storage, naming the storage and the moment it was released. They are warnings in this release and become errors in 1.3;--strict(orNX_STRICT=1) makes them errors now. - A warning channel: the checker's warnings print like errors with
warning:in front, the language server reports them with warning severity, and the harness runs compile-fail cases marked// STRICTunder--strict. @escape(v): a copy of a value made by the allocator outside the innermostusing arenablock, the one way a value leaves the block.- The fuzzer builds every tenth accepted mutant in debug mode (released storage filled with
0xDD) and runs it: a signal or an abort is a finding (nx run tests/fuzz.nx -- --run 10, as CI does). - Every
unsafeblock inself/andstd/carries a reason comment. nxsays once a day when a newer release exists, the way npm and Deno do: after a command has done its work, one line on stderr names the release andnx upgrade, and the REPL's banner names it too. The question to GitHub is asked at most once a day (kept in~/.nexium/update-check,%LocalAppData%\Nexium\update-checkon Windows, or beside a portable copy) and never in CI or withNX_OFFLINE=1;NX_NO_UPDATE_CHECK=1silences it. The harness checks the notice, its once-a-day silence and the switch.
Changed#
- The Windows installer's welcome page shows the mark over the social preview's topographic contours (
scripts/make_wizard_images.pydraws them). - The six translated READMEs (es, zh-CN, ja, ko, fr, de) caught up with the English one: the install roads (PowerShell, pip and npm, Docker, Homebrew and Scoop), the commands that arrived in 1.0.3 (
--watch,explain,audit --lock,layout,upgrade,install), the--cpuflag, the status line, the languages table and the layout. - The Windows installer's "More from Londopy" page lists statusmith first (Discord Rich Presence from the tray, with its SDK as a Nexium package) and fits its four rows on the page: shorter blurbs, a "GitHub" and a "Download" button each.
Fixed#
- A struct literal evaluated its initializers in declaration order while the checker had checked moves in the order written, so
Pair{ .first = name[..], .name = name }withnamedeclared first read a zeroedname. Initializers run in the order written (spec cases6_literal_written_order, decision 105). - A
return,breakorcontinueout of ausing arenablock leaked the arena. It ends with the block's scope on every exit (the harness runss5_arena_escapeundernx leaks). nxwith no arguments at a terminal read its padded argument list through a view into a list released at the end of an inner block, and the loader read a module's name after moving the string it was a view into. Both worked by luck; the new rules found them.
[1.1.0] - 2026-09-21#
Annapurna: Dutch Rib — the 1977 route on the north face that became the everyday line because it is safer than the Sickle: the language the compiler wanted. ?. reads through an optional, let (a, b) = pair and for (k, v) in m take tuples apart, derive(Clone) gives a struct its deep copy (the compiler's own hand-written copies are gone), any value with next(self: *mut Self) -> ?T iterates, slices match by shape with [first, rest..] and whole @ pattern, guard facts flow through while and else and prove != 0, != null, s.len >= n and i < s.len, format arguments have names and a width from a value, and @alignOf, @bitCast, @min, @max and @target() arrive. Found on the way and fixed: place.? and place orelse d dropped the payload twice in a read, and a guard fact outlived the loop that changed its variable, a bounds check elided since 0.6.0. The seed is regenerated from the final sources.
Added#
?.reads through an optional:opt?.fieldandopt?.method(args)arenullwhenoptis and the member as an optional otherwise; the rest of a postfix chain applies to the payload (a?.name.len orelse 0), an optional result is not wrapped twice (a?.b?.c), and a chain over a temporary owns and drops the payload. It is theif letit stands for in the typed IR, so the emitter and the interpreter learned nothing new; the formatter, the tree-sitter grammar, the spec and the Topo know the operator. The first item of 1.1.- Tuple destructuring:
let (a, b) = pair,var (x, y) = f(), andfor (k, v) in pairs(an index may follow,for (k, v), i in pairs), with_skipping an element. Over an owned value the names own the elements; over a place they are views of it, as anif letbinding is (decision 92). Each name is oneletin the typed IR. Spec cases5_tuple_destructuring; the second item of 1.1. derive(Clone)gives a struct or enum.clone(), a deep copy field by field, once every field and payload clones (numbers,String,List,Map, slices, optionals, tuples, arrays, weak references, otherClonetypes, reference classes by retaining); a pointer, a trait object or a function value does not, and the error names the field. Tuples, optionals and arrays of clonable things clone without a derive, andwhere T: Clonebounds a type parameter. Closes theKNOWN_ISSUES.mdentry: the compiler's hand-writtencv_cloneandframes_cloneare gone (decision 93). Spec cases8_derive_clone; the third item of 1.1.- Iterators:
for x in it { }drives any value with anext(self: *mut Self) -> ?Tmethod until it yieldsnull, owning the iterator and each yielded value, withbreak,continueand labels as in any loop (it is thewhile/if letit stands for in the typed IR, decision 94).for (k, v) in m { }walks a map's entries. Spec cases6_iterators; the fourth item of 1.1. - Slice patterns in
match:[],[x],[a, b],[first, rest..],[.., last]on slices and arrays, the rest a[]Tview of the middle, exhaustive by length ([]with[x, rest..]needs no_); andwhole @ pattern, the value under a name while its parts match (decision 95). Spec cases7_slice_patterns; the fifth item of 1.1. - Guard facts flow into the body of a
while(at the top of every pass) and, negated, into theelseof anifor awhile; a guard also provesy != 0forx / y,x != nullforx.?,i < s.lenfors[i], ands.len >= nfor indexing and slicing withinn, so fewer functions carrypanics(decision 96). Closes theKNOWN_ISSUES.mdentry on range facts. Spec cases9_guard_proofs; the sixth item of 1.1. - Named format arguments:
println("{name} is {age}", .{ .name = n, .age = a }),{name:spec}for a spec, an argument written more than once, and a width from an integer argument,{v:>w}with.w = 8(decision 97). Format arguments are now evaluated once each, in order, before the first write. Spec cases6_formattinggrew; the seventh item of 1.1. - Intrinsics
@alignOf(T),@bitCast(T, x)(scalars of one size, throughmemcpy),@min(a, b)and@max(a, b)(which carry the range of their operands, so@min(i, n - 1)proves an index), and@target(), the(os, arch, pointer_bits)of the C build (decision 98). Spec cases12_intrinsics; the eighth item of 1.1.
Fixed#
- A guard's fact about a
varsurvived into a loop that changed the variable (if i < 3 { while i < 6 { xs[i]; i += 1 } }elided the bounds check onxs[i]and read past the array, withnx effectsreporting nopanics), since 0.6.0. Facts about mutable locals now end at an assignment, at a mutable borrow, and at the entry of any loop. Spec cases9_facts_end_at_loops. place.?,place orelse dandtry placeused where nothing takes the value (a print argument, a.len) dropped the payload twice: once as the read's temporary and once with the place. The read is now a view of the place's payload, and only anorelsedefault is a temporary of the read's own. Spec cases6_optional_reads.- The Release and Bench workflows could not commit the package-manager manifests and the numbers to a protected
main(both jobs of v1.0.3 failed on the push, the release itself was fine). A refused push now puts the commit on a branch (packaging/vx.y.z,numbers) and the job says how to land it; the site reads the numbers page from that branch, the next Bench run measures against it, and the release steps inCONTRIBUTING.mdname the landing.
[1.0.3] - 2026-09-20#
Annapurna: Schatz — the one who found the summit party in the crevasse the morning after: the long-hidden bugs, and then the roads in. An outside review of 1.0.1 read the code and found five bugs that had been there since their features shipped: a contained panic leaked what the call acquired, the lockfile pinned nothing, the Python wrapper dropped writes to a mutable slice, effect notes pointed at the function instead of the line, and SECURITY.md promised more than the specification did. All five are fixed here, the compile-time interpreter is freed from its 32-call limit, and the roadmap's quick wins that followed are in too: every way to install (a one-liner on each platform, pip and npm, Homebrew, Scoop, winget and Chocolatey, Docker, torrents), nx upgrade, nx install, nx layout, nx explain, the effects lockfile, code lenses with the panic proof, the REPL's line editor and one-liners, watch mode, the numbers page, and the first package from outside the tree.
Added#
- The Windows installers, the compiler's own and the ones
nx shipwrites, refuse to start while another installer or uninstaller of the same program is open and name it, close the program if it is running before replacing its files (Restart Manager; it is not started again), and keep a log of the run asinstall.lognext to the program. The uninstaller takes/LOG="path"for a log of its own. nx run FILE --watchandnx test FILE --watchrun again whenever a file of the program changes: the modules the checker loads (the program's own and its packages', not the standard library), polled twice a second by their modification times.NX_WATCH_ROUNDS=Nstops after N reruns, which is how the harness checks it.- The numbers page:
bench/holds four programs (fib,nbody,sieve,words) written the same way in Nexium, C, Rust, Go and Python, andbench/run.pybuilds and times them, checks the answers agree, and writes the medians with the machine and the tool versions to JSON and todocs/numbers.md; the Bench workflow runs it on a GitHub runner weekly and at every tag, commits the numbers, and fails when Nexium got a quarter slower than the last run. nx explain FILE f effectprints whyfhas an effect as a tree: the reason the checker recorded in each function (an allocation, an overflow check, an explicit panic, with its position) and the calls that carry the effect in, each with the callee's own tree, down to the primitive that brings it.- The REPL has a line editor at a terminal: the line colours as you type, Tab completes from the language server's completion at the cursor, the arrows and Home/End move, Up/Down walk the history, the usual control keys cut and clear, Ctrl-C drops the line. Raw terminal input comes from three builtins,
io.raw_mode(on),io.read_key()andio.pending_input(); the console is raw only while a line is typed.NX_PLAIN=1asks for the plain reader,NO_COLORfor no colours. - The language server puts a code lens above every function with its panic proof,
cannot panicorpanics: <why> at :<line>from the witness the checker recorded, followed by the rest of its effects. - The effects lockfile:
nx audit FILE --lockwritesFILE.effects.lock, every function's effects (the program's own modules and its packages, not the standard library), andnx audit FILE --checkfails when a function gained an effect the lock does not name, noting what else moved. The shipped example's lock is committed and CI checks it. - Portable mode: a file named
portablebeside the executable keeps Zig's global cache beside it (cache/), so a copy on a USB stick leaves nothing on the host, andnx doctorsays so.nx install [DIR]puts the copy in the user's place (%LocalAppData%\Programs\Nexium, or~/.nexiumwithbin/andshare/) with the zig, examples, std and docs beside it, and adds the directory to the user's PATH unlessNEXIUM_NO_MODIFY_PATH=1: the portable zip installing itself. nx upgradeinstalls the latest release in place of the running executable: the archive for the machine, verified against the release'sSHA256SUMS.txt, unpacked withtar, moved overnx(set aside asnx.exe.oldon Windows until the next upgrade);--checkonly reports, andnx doctornow points at it when a newer release exists.- statusmith's Discord Rich Presence SDK is the first package from outside the tree:
docs/discord.mdhas the card's fields and the install line (nx add discord_rpc --git https://github.com/Londopy/statusmith --tag sdk-v0.1.0 --dir nexium), the Topo's packages chapter adds it with a program the suite runs when online, and the catalogue indocs/packages.mdlists it. - A git dependency may name the package's directory inside the repository:
sdk = { git = "...", tag = "v2.0.0", dir = "nexium" }, ornx add sdk --git URL --tag TAG --dir nexium, for an SDK that lives beside the app it belongs to. The packages suite fetches one. - The Windows installer detects an installed Nexium: the page after the welcome names its version and directory and offers the upgrade (a repair when it is the same version, a replacement when the installed one is newer), with the previous directory, components and tasks as the defaults, or the removal of the installed version, which runs its uninstaller and exits.
pip install nexium-langandnpm install -g nexium-lang: every release carries the compiler as a wheel per platform (the binary undernexium_lang/bin/, a console scriptnx) and as npm packages (nexium-langwithbin/nx.js,@nexium-lang/<os>-<cpu>as optional dependencies holding the binaries), built byscripts/pypi_npm.pyfrom the release archives, attached to the release, and uploaded to PyPI and npm when thePYPI_TOKENandNPM_TOKENsecrets are set.std.testinghasexpect_snapshot(name, actual)andexpect_snapshot_in(dir, name, actual):snapshotin theexpect_form, so a mismatch fails the test naming the file, the first differing line and how to accept the new output (NX_UPDATE_SNAPSHOTS=1), and a file that cannot be read or written fails it too.nx layout FILE [Type...]prints the C layout of a struct (every field's offset and size, the padding, the total, and the order by alignment that would shrink it, since the backend keeps declaration order) or an enum (the tag, where the payload starts, each variant's payload); without a name, every non-generic struct and enum of the file.nx -e CODEruns lines as the prompt would andnx -p EXPRprints the value of an expression on its own; the interpreter runs them, so nothing is compiled.- The REPL has
:undo(the last line taken back, bindings and all),:save FILE(the session as a programnx runruns; values printed at the prompt become_ = ...) and:effects EXPR(the effects of an expression, checked in a function of its own with the kept bindings as parameters).:load FILEwas there already. installers/install.ps1, the Windows one-liner:irm https://raw.githubusercontent.com/Londopy/nexium/main/installers/install.ps1 | iexdownloads the portable build for the machine, verifies it against the release's checksums, installs it under%LocalAppData%\Programs\Nexiumon the user's PATH and downloads Zig beside it when no C compiler is found;NEXIUM_VERSION,NEXIUM_HOME,NEXIUM_NO_MODIFY_PATHandNEXIUM_NO_ZIGas in the shell script. A Chocolatey package (installers/chocolatey/, written byscripts/packaging.py) wraps the installer with its checksum; the Windows build job packs it, attaches the.nupkgto the release, and pushes it to chocolatey.org whenCHOCO_API_KEYis set.ghcr.io/londopy/nexium: the compiler with its Zig toolchain, the standard library, the examples and the docs, on Debian (latest) and Alpine (:alpine), for amd64 and arm64, built from the release's seed at each tag (docker/,.github/workflows/docker.yml); the entry point isnx. CI builds both images and runs hello inside on every push.- The Windows installer offers "Open Nexium REPL here" in a folder's right-click menu beside the console entry, and a "Nexium REPL" profile for Windows Terminal, written as a JSON fragment (per user or for all users with the install mode, removed at uninstall) and offered checked when Terminal is installed.
docs/packages.mdhas the list of packages one cannx addtoday, what a repository needs to be one, and how to get a row; it is the registry until there is a registry.- The release workflow publishes the VS Code extension to the Visual Studio Marketplace and to Open VSX when the
VSCE_PATandOVSX_PATsecrets are set, and says so when they are not; the extension is packaged with the release's version so each release publishes once. nx doctorsays when a newer release exists: it asks the GitHub API throughcurl(three seconds at most) and prints the latest tag beside its own version;NX_OFFLINE=1skips the question, and nocurlor no answer is reported rather than failed.- Homebrew, Scoop and winget: the repository is its own Homebrew tap (
brew tap londopy/tap https://github.com/Londopy/nexium, thenbrew install londopy/tap/nexium; the release build on Apple Silicon and Linux, the compiler built from its one C file elsewhere) and its own Scoop bucket (bucket/nexium.json, also installable by URL), andinstallers/winget/holds the manifests forLondopy.Nexium, ready for microsoft/winget-pkgs and usable withwinget install --manifesttoday.scripts/packaging.pywrites all three from a release's checksums, and the release workflow commits them tomain. - Releases attach a
.torrentfor every file, with its GitHub download as the web seed, and the notes list the magnet links with a QR code of each (scripts/release_notes.py; the release job installssegno). - The install script builds
nxfrom the one C file,bootstrap/nx.c, with the C compiler on the machine when no release is built for it (an x86-64 Mac, a BSD, a RISC-V board), when the download fails, or whenNEXIUM_FROM_SOURCE=1asks for it; the one-C-file install is on the front page of the docs and indocs/install.md. artifact cli { stack = "1G" }:mainruns on a thread reserving that much stack ("64K","256M","1G"or a byte count), so a recursion deeper than the platform's default completes; the reservation is address space, committed as the program reaches it, and a 32-bit process caps it at 256 MB. The compiler declares a gigabyte for itself, and compile-time evaluation may now nest 1000 calls (it stopped at 32 since the fuzzer found the unbounded case; each nested call costs about 165 KiB of the compiler's stack). A spec case recurses 300,000 deep at run time and 300 deep at compile time.- A Sublime Text build system,
editors/sublime/Nexium.sublime-build: Ctrl+B runs the file, the variants test, check, build and print its effects, and a diagnostic's--> file:line:colline is clickable. - The roadmap's "run from the editor, everywhere": the same Ctrl+B in every supported editor, a one-line
--format shortdiagnostic for problem matchers and--format jsonfor tools.
Changed#
SECURITY.mdstates the promise precisely: deterministically memory-managed without a garbage collector, memory safety complete in 1.2 when the view rules land, and a host is not left holding what a panicked export call acquired. Page titles say "Nexium language" (decision 91:nexium-langfor the language's own packages on registries wherenexiumis taken).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. 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. An illegal instruction from the probe is named as such, with the fix. The harness runs the check.
Fixed#
nx doctorandnx upgradesendGITHUB_TOKEN(orGH_TOKEN) to the GitHub API when the environment has one, so a CI runner is not rate-limited into "no answer"; the harness counts a silent API as a note, not a failure, and fetches the Topo's Discord example only on Windows, where the SDK's pipe lives today.- A
catchororelsehandler whose tail is a panic, on a value with a struct type (aString, say), emitted C that did not compile (_t = 0for a struct): the tail was typed as the value the block should have, by coercion, and the generator assigned its placeholder. Found byexpect_snapshot's own test; a spec case keeps it. - On a macOS or Linux machine without Zig,
nxtakescc,gccorclangfrom the PATH instead of running azigit assumed was there;nx doctornames the choice. A gcc-only Ubuntu could buildnxfrom the one C file and then compile nothing.-lpthreadis linked on Linux for glibc before 2.34. CI builds hello with zig removed from the PATH. - The install script no longer stops with
line: parameter not setat the end whenNEXIUM_NO_MODIFY_PATH=1is set.
[1.0.2] - 2026-09-20#
Annapurna: Terray — the one who carried the frostbitten summiters down: the hotfix. The x86-64 binaries of 1.0.0 and 1.0.1 were built for the CPU of the machine that built them and crashed on any other; every build is for the architecture's baseline now, and the release refuses one that is not. With it, the fuzzer's two fixes, UTF-8 on the Windows console, and the day's documentation.
Fixed#
- A panic caught at an export boundary releases what the call acquired. The wrapper's
longjmpskipped every drop between the panic and the boundary, so a library call that built a string and divided by zero leaked the string, and one that opened a file leaked the handle, on every call. Each export call now runs with a tracker: its allocator records live allocations (arena chunks included), and file, socket and lock handles register as they are acquired; the panic path releases them all before the status goes back. Threads started inside the call keep what they allocated, since it can escape throughshared_mutable. The harness ships a library whose export opens a file and panics, calls it 200 times (the runtime has 64 file slots) and checks the next open succeeds. The first half of the roadmap's 1.2, "a panic releases what it owned". fs.remove_allremoves read-only files on Windows (every object in a git checkout is one), whereremoverefused them and a directory tree was left half deleted.nexium.lockpins dependencies.nx fetchwrote the commit each git dependency resolved to and never read it back, so a fresh clone got whatever the tag pointed at that day and the lock's promise was empty.nx fetchnow checks a locked dependency out at the locked commit (a shallow fetch of that commit when the tag has moved), rewrites the lock only for dependencies it had to resolve, and refuses a locked commit the source no longer has with the command that resolves the tag again. The harness builds a git dependency, moves its tag, fetches from the lock on a bare tree and checks the old commit is what runs.- The generated Python package took a list, a read-only buffer, an array of another item type or a strided view for a
[]mut Tparameter, copied it into a temporary and lost the writes; each is aTypeErrornow that says what to pass. A[]mut Tparameter takes any writable, contiguous buffer of the matching item format (bytearray,array.array, a writablememoryview, a NumPy array) and writes through it; a read-only slice parameter takes any buffer by pointer when the format matches, otherwise by copy, and any sequence by copy. CI calls the shipped library with all of them. - The
!effectdiagnostic's notes point at the recorded witness (theappend, theprintln, the call) rather than at the header of the function that holds it; the README's "exact line" is true again. The compile-fail cases now pin the line and column of the notes, which is how the wrong position went unnoticed.
Added#
nx update [name...]: resolve the tag of every dependency (or of the ones named) again and rewritenexium.lock; the counterpart ofnx fetch, which follows the lock.--cpu baseline|native|<name>andNX_CPU, the CPU a build without--targetis for;nx doctorprints it.os.arch(), the architecture a program runs on.- Nexium programs print UTF-8 on the Windows console: the generated
mainswitches the console to code page 65001 for the program's life and restores it at exit, sonx versionshowsRébuffatrather thanR├⌐buffat. - The roadmap's "tools only this language can have":
nx explainfor an effect's provenance, an effects lockfile for CI, proofs as editor code lenses, profiling by effect, record-and-replay tests,nx layout, the ownership trace, asking the checker for its facts, a binary-pattern debugger and workbench, hot reload, one-liners, REPL sessions as files, watch mode, snapshot tests, a Jupyter kernel and the playground, each pencilled into a minor. - The roadmap names what 1.0 is not yet (memory safety, maturity, numbers, ecosystem) and where each is answered; the numbers page (benchmarks against C, Rust, Go and Python, published on the site) moves from 1.6 to 1.1; a second maintainer and a list of packages are ecosystem items; the Always section commits to the corpus growing with the language, the sanitizers and the fuzzer on every release, and a patch within a week of a fix.
- The README's Status section describes 1.0 (language-stable, early ecosystem, with the roadmap's ledger of what it is not yet) instead of the first implementation; the six translated READMEs are regenerated from the current English one, which had moved on to the self-hosted compiler, the site, the Topo, the editors and the 1.0 numbers while they still described
cargo installand a Rust compiler. - The
CIworkflow skips a push that changes only prose and pictures; thePagesworkflow builds the site from them and validates the changelog, so a roadmap edit no longer runs the compiler on three platforms.CONTRIBUTING.mdsays what runs on a push. - The roadmap's page zero:
KNOWN_ISSUES.mdis where every open bug waits with its fix, no theme starts over a bug a user can hit, and the four an outside review of 1.0.1 found (a contained panic leaks what it acquired,nexium.lockis never read, the Python wrapper drops writes through a list, effect notes miss their witness) are the 1.0.2 patch, with the wording fixes forSECURITY.md, the README and the package names; 1.3 begins by splittingcheck.nx. - The roadmap opens with the quick wins in order: twenty-nine items sorted by how soon each can ship (hours, a day or two, about a week), each landing as a patch or in 1.1 when done, ahead of the themes.
- The roadmap's niche roads: the PowerShell one-liner (
irm | iex), the one-C-file install (curl bootstrap/nx.c | cc), torrents with web seeds and magnet links, IPFS pins,nx installfrom a portable copy, portable mode, MSIX with an.appinstaller,gh release downloadand the configuration-management modules, and a QR code. - The roadmap's "every road in": the installation channels beyond the setup and the script, per platform (winget, Chocolatey, Scoop, MSI, MSIX; Homebrew, MacPorts, a notarized
.pkg; apt and dnf repositories, PPA, COPR, OBS, AUR, Alpine, Nix, Guix, Snap; BSD ports), containers and dev containers, wheels and npm packages that carry the binary, version managers andnxup, the extension registries, and the same channels as targets of theinstallerartifact for Nexium programs. - The roadmap's 1.5 gains 32-bit architectures:
i686Windows and Linux,armv7,riscv32andthumb, with the list of places 64 bits are assumed today (runtime handles and sizes, 64-bit binary segments on a 32-bit word, the compile-time interpreter'susize, the C header import'slongandsize_t), the cross-compiled test run in CI, the compiler self-hosted as 32-bit, and the tiers. - The roadmap's Topo as a course and the docs, more: exercises the compiler grades (fill in the blank, fix the error, write it), quizzes checked against
nx effectsand the recorded outputs, predict-the- output cards, code blocks that run in the page once the compiler runs in the browser, progress on the route map, more project chapters; std reference pages fromnx doc, an error index, a cookbook, in-depth guides, a style guide, site search and docs per release. - The roadmap's "installer grown up": a setup mutex against two wizards at once, upgrade/repair/remove of an installed version, closing a running
nxbefore files are replaced, checks for disk space, a pending reboot and antivirus quarantine, the toolchain choice, the wizard in six languages, Authenticode signing, logs and rollback, silent-install parameters and an MSI, an ARM64 setup, and a Check for updates entry. - The roadmap's clickable line: the REPL with an inspector window,
:plotand:watch, the installer's editor page andnx upgrade,nx topoas an interactive tutorial runner,std.tui, the visual tools (size treemap, leak explorer, effect graph, ownership timeline), nexium-gui grown up, and the Hut, a native IDE in nexium-gui.
Fixed#
- The x86-64 release binaries of 1.0.0 and 1.0.1 crashed with an illegal instruction on machines without AVX-512 (
nx versionandnx doctorworked; anything that compiled did not):zig cccompiles for the CPU it runs on, and the release runner's had AVX-512. Every build without--targetis now for the architecture's baseline (plain x86-64) unless--cpu nativeorNX_CPUasks for more; the bootstrap scripts do the same; the release workflow and CI refuse an x86-64 compiler that contains AVX instructions. - A module name used as a value (
let x = math,var t = thread) crashed the checker with an index out of bounds; it is the diagnostic "mathis a module, not a value" now (found by the fuzzer in CI). - A compile-time call chain that never bottoms out overflowed the compiler's stack (found by the fuzzer, which moved the call to
fibintofib); the compile-time interpreter stops at 32 nested calls with a diagnostic. The limit is low because each nested call costs about 165 KiB of the compiler's stack;KNOWN_ISSUES.mdhas the fix that raises it. - The documentation site's menu no longer jumps back to the top on every page: it is its own scroll box, and a page load reset it, so reading the chapters in order meant scrolling the menu down again each time. The menu now keeps its position from page to page and brings the current page's entry into view when it is not.
- The links under "Files" in the release notes were relative, so GitHub resolved them under the tag page and every one was a 404. The notes script writes the download URL now, and the notes of every release since 0.2.0 were corrected in place.
[1.0.1] - 2026-09-20#
Annapurna: Rébuffat — the guide who roped the snow-blind party together on the descent: a patch that ties the loose ends after the summit. The fixes the tutorial found, the documentation site and the Topo, and support for eleven editors; the language is the same.
Added#
- Editor support beyond VS Code and Sublime Text, under
editors/: Vim (syntax, indent,:NxRunand friends,:make), Neovim (a plugin that registers the tree-sitter parser, its queries andnx lspwith the built-in client), Helix (language config and queries in Helix's scopes, with indents and text objects), Zed (an extension with grammar, outline and the language server), Emacs (nexium-modewith Eglot and lsp-mode registration), Kate and the other KSyntaxHighlighting editors, Notepad++ (a User Defined Language), nano, and a guide for JetBrains IDEs (TextMate bundle plus LSP4IJ). CI loads or compiles every one of them. - The documentation site, londopy.github.io/nexium, built by
site/build.nx, a Nexium program that renders the Markdown in the repository (the docs, the project files, the tutorial) with the language's own token rules for highlighting, and published by a Pages workflow on every push. The test harness builds it. - The Topo,
topo/: a 23-chapter tutorial from installing the compiler to a neural network, a GUI, a network service and a library shipped to C, Python and Node. Every program in it (topo/code/) is run by the harness against its recorded output, and the diagnostics it shows are recorded too. Writing it found the bugs below. - The documentation site on a phone: one column with the navigation behind a Menu button, tables that scroll sideways instead of widening the page, grids and code blocks that stay inside the screen, larger touch targets, and a theme colour for the browser chrome. Every page is checked at 375 pixels wide.
assets/social-preview.svgand its 1280x640 PNG, the card GitHub and the chat apps show for a link to the repository (uploaded under the repository's settings); every page of the documentation site carries Open Graph tags pointing at it.- The roadmap's 1.7, the seam both ways: Python (strings, NumPy without copies, dataclasses and exception classes from the Nexium types,
ref classhandles, callbacks, a CPython extension wheel, Python from Nexium), Rust (error sets as enums, strings and handles,no_stdcrates, source crates, Rust crates from Nexium), C++, Go, Java and Kotlin, C#, Ruby, Lua, Swift and the browser asartifactkinds, a versioned ABI document, buffers that leave,nx ship --abi-check, and@cImportaccepting more of C. Release name Annapurna II; Gangapurna moves to 1.8. - The roadmap's 1.2, memory safety without a garbage collector: the view rules V1 to V5 (returning, storing, growth, moving, arenas),
unsafefor what the checker cannot prove, what stays (moves, scope-exit destruction, reference counting,weak, leak detection) and how the rules roll out under the stability policy, which now says that undefined behaviour may become an error in a minor. The later themes move down one number.
Fixed#
- A function checked on demand while a constant was evaluated at compile time could name a global before its type was resolved, and the checker crashed on the unresolved type (found by the fuzzer on its second run in CI). A global's type now resolves on first use; the program gets the diagnostic for the compile-time global access instead.
- A bare
break,continueorreturnends amatcharm before the comma (_ => break,parsed the comma as an expression). A one-element array literal can be indexed and sliced ([x][..],[x][0]): only a[that opens[]Tor[N]Twith a type after it starts an array type in expression position. Set!Twidens into!Twhere!Tis expected (an initializer, an argument, a return);!Tnever narrows into a named set. Type names now spell a named set (Parse!i32), so the diagnostic no longer reads "expected!i32but found!i32".- The
!effectdiagnostic's first note points at the call that brings the effect in, not at the function's header. - Two dangling views, found by running the tutorial's programs on Linux and the compiler under AddressSanitizer:
std.http.parse_urlkept the host as a view of a value that was released when theif letended, so every URL with a port could fail withNotFoundwhere the freed memory was reused; and the checker read a syntax node through a pointer after adding nodes to the tree (a binary pattern with a computed size). Both are the case the roadmap's 1.2 makes an error; a CI job now builds the compiler with the sanitizers, checks every source with it, and runs every spec case and tutorial program built with them. for k in mover aMapemitted C that did not compile: the keys are collected into an ownedListthe loop walks and releases, the same asfor k in m.keys().m[key]crashed the emitter; it is the lookupm.get(key)is. Both have a spec case now.@weak(x)did not parse (weakis a keyword); a builtin may be spelled with one. A weak reference created in a struct literal was retained a second time on its way into the field and its storage never freed; and an object whose fields held a weak reference back to it could be freed while its own fields were still being released. Aref classnow holds its storage until its fields are gone; both cases are spec cases.
[1.0.0] - 2026-09-20#
Annapurna: Summit — the top of the mountain the project has been on since 0.1: the language stops changing under people's feet, the compiler is written in itself, and nothing but Nexium, one C file and a runtime header is left.
Removed#
- The first compiler, written in Rust (
bootstrap/rust/,Cargo.toml,Cargo.lock), and with it the last Rust toolchain use in CI: thelintjob now runsnx fmt --checkon the tree andnx checkon the compiler, the harness and the fuzzer. The repository is Nexium, one generated C file, and the runtime header (decision 90). The git history keeps the crate.
Changed#
- The architecture tour (
docs/architecture.md) names the files underself/; the specification's status table and the roadmap record that the port is complete.
Fixed#
- The fuzzer wrote its cases under
nx-out/fuzz, the path of its own executable on Linux and macOS, so CI's fuzz job could not start; it works undernx-out/fuzzing.
[0.9.0] - 2026-09-20#
Annapurna: Summit Ridge — the last ridge, nothing left but walking up: every tool in Nexium, the shipped nx the Nexium one, a harness and a fuzzer in Nexium, the stability policy and the tiers.
Added#
nx fmtin Nexium (self/fmt.nx, a command ofself/nx.nx): the formatter, byte for byte the same as the first one on every source in the tree with its layout disturbed, whichcargo testchecks while the Rust one exists. The lexer keeps//comments as tokens on request.nx effects,audit,refcounts,leaks,size,versionanddoctorin Nexium (self/tools.nx,self/size.nx): the reports over the checked program and the section-table reader for ELF64 and COFF binaries, matching the frozen Rust commands on every example except where the Nexium compiler does better (dynandfntypes printed as written, recursive functions showingunbounded_stack).nx docin Nexium (self/doc.nx): the same page as the first generator on every source, apart from a function's effects coming from its own module and generic functions saying so instead of showing one instantiation. The parser keeps each///run's text (Tree.docs).- Packages in Nexium (
self/manifest.nx): thenexium.tomlsubset,nx init,nx add,nx fetchand the lock file; the loader resolvesimport depandimport dep.modulethrough the manifest and scopes a package's own imports to itssrc. nx shipin Nexium (self/ship.nx,self/ship_node.nx,self/installer.nx): the C header, shared and static libraries, the Python package and wheel, the Rust crate, the npm package and the installers, from the export recordscgen.nxwrites as it emits each wrapper. The header and typings carry each function's///comment.nx lspin Nexium (self/lsp.nx): the language server over stdio, with diagnostics, hover, go to definition, completion and rename, and the editor-module loader and IDE queries behind it;cargo testdrives it the way an editor does.nx replin Nexium (self/repl.nx): the interactive session on the compile-time interpreter, which in REPL mode may print, read the console, touch files, the clock and randomness (check.nxrepl_mode,run_repl, values rendered by their types).nxalone at a terminal opens it. The last tool leaves the Rust crate.io.is_terminal(h),os.set_env(name, value)andos.exe_path(); with the last,nxfinds a Zig bundled next to it (<dir>/zig/zigor<dir>/../zig/zig), as the Rust driver did. The driver gives each build its own Zig cache under the output directory whenZIG_LOCAL_CACHE_DIRis not set, as the Rust driver did, since Zig's cache is not safe against severalzig ccstarting at once on Windows.- The seed
bootstrap/nx.cis regenerated whenself/needs a builtin the seed lacks, not only at a release. - The test harness is a Nexium program,
tests/run.nx: it builds the compiler from the seed (when the sources are newer than it), checks the fixed point, and runs the examples, the spec cases, the compile-fail cases,fmt --checkon the tree, the standard library's tests, the GUI, packages,ship, the installer, the REPL and the language server, the cases on several threads.cargo testand the Rust harness retire; CI's test jobs have no Rust toolchain. On Windows a program run throughprocessmay be named with forward slashes. - The stability policy,
docs/stability.md: what a patch, a minor and a major may change from 1.0 on, the deprecation cycle, what is not covered, and the C toolchain policy.nx fix FILE...applies the deprecations the compiler can migrate mechanically; there are none yet. - The platform tiers,
docs/platforms.md: tier 1 is built, tested and released (x86_64 Linux and Windows, Apple Silicon); tier 2 is built and released, not tested (aarch64 Linux, Windows on ARM), cross-compiled from the same C in the release workflow and picked by the install script on an ARM Linux; tier 3 is whatever Zig targets. - Fuzzing,
tests/fuzz.nx: mutated corpus sources through the front end and random bytes through the binary pattern engine (tests/fuzz_patterns.nx); a crash, a signal or a hang is a finding, saved for the report. CI runs it on every push with a fresh seed. - Releases ship the compiler written in Nexium: the release workflow builds it from the C seed on each platform with Zig alone (
ccon macOS), checks the tag againstself/nx.nx, and packages that binary in the archives and the Windows installer. Building from source issh bootstrap/build.sh; no Rust is needed.
Fixed#
if let v = optover a place (a local, a field, an element) bound a bitwise copy of the payload that could then be moved on, so the optional and the binding both freed it. The binding is now a view of the payload, like a loop variable: moving out of it is an error with a.clone()/.?hint, while a binding over an owned temporary (a call result) still takes the payload. The comptime interpreter and the language server had such copies.- Reading standard input through
std.stream.Readerblocked until 64 KiB arrived or the pipe closed, sincefreadfills its whole count; the runtime now reads the descriptor and hands over what the pipe has, so a server on stdin answers each message as it comes.io.read_lineand the reader share one buffer. Standard input is binary on Windows, like the other two streams.
[0.8.0] - 2026-09-20#
Annapurna: the Sickle — the exposed glacier crossing below the summit: the compiler in Nexium is the compiler, and builds from its own C with no Rust.
Changed#
- The compiler written in Nexium is the compiler (decision 90). The first compiler, in Rust, is frozen at 0.7 semantics and moved to
bootstrap/rust/; it still builds and still holds the tools not yet ported (fmt,doc,lsp,ship, packages, the REPL), and leaves at 1.0. A machine with nonxbuilds one frombootstrap/nx.c, the C the compiler emits for itself, with any C compiler and no Rust (bootstrap/build.sh,build.ps1); CI does so on three platforms with no Rust toolchain.cargo testbuilds the compiler from the seed and runs every example, spec case and compile-fail case through it; the oracle diffs that drove the port (nx tokens,sexp,tirandemit-ccompared between the two compilers) retire.
Added#
unbounded_stackis an effect, no longer reserved: a function on a cycle of the call graph (it calls itself, or calls something that calls it back) carries it, because its stack use depends on its input; calls through function values and trait objects acquire it as they acquire every permitted effect.!unbounded_stackis discharged by rewriting the recursion as a loop over an explicit stack (SPEC 9). The first language change made in the compiler in Nexium alone.- The compiler in Nexium renders diagnostics as the tools always have: the message,
--> path:line:col, the source line, a caret, notes under their error, and the count at the end; it printed byte offsets.
Fixed#
- The compiler in Nexium searched no directory for
@cImportheaders and vendored C when the file was named without a path (nx build demo.nxfrom insidegui/); the file's directory is.in that case, as in the Rust compiler.
Removed#
layout(packed)andsoaon structs, and thepoolandstackallocation strategies, are gone from the specification (decision 88):layout(c)andusing arenaare what the language has. The two struct spellings used to be accepted and silently ignored; they are errors now. Region rule R1 is the region rule; the cases it does not cover are listed in SPEC 5.6, and a debug build now fills freed storage with0xDDso a view that outlived its storage does not read the old contents by luck.
[0.7.0] - 2026-09-19#
Annapurna: Camp V — the last camp, at 7,400 m; the summit push starts here: the compiler builds itself.
Added#
self/check.nxchecks closures: parameter and return types from the expected function type, by-value and by-reference captures, inferred return types; and records: constraint checks, compile-time decisions on constant fields,Record.new; and trait objects: vtables,dyncoercions, dynamic calls, effect bounds on function values.examples/generics.nx,examples/tests.nx,examples/tour.nx,examples/records.nx,examples/dyn.nxandstd/thread.nxjoin the body comparison; and binary patterns and construction:examples/binary.nxandexamples/binary_sizes.nxjoin too (46 sources).examples/records.nx: compile-time,.newand run-time constraint checks.examples/binary_sizes.nx: float, signed, little-endian and computed-size segments, a remainder written back out.self/check.nxevaluates at compile time: an interpreter over the typed IR runs constants, globals,comptimeexpressions,comptime testblocks and record constraints;examples/comptime.nx(a CRC table, primes, strings and structs computed at compile time) joins the comparison (50 sources: every example and std module but@cImportusers).- A constant slice whose elements were computed at compile time is stored as a static array; only string literals could back a constant slice.
self/check.nxreports the diagnostics-only passes: declared effect bounds against inferred effects,for parallelbodies mutating shared state, mutable globals in embeddable libraries,ownon copied types and on exported or fn-value functions, moves out of map lookups,matchexhaustiveness, returned views into locals.cargo testruns it over every compile-fail case and expects every message the Rust checker gives.self/cimport.nx, the C header importer in Nexium, and@cImportinself/check.nx: every source in the tree now matches the oracle,examples/cimport.nxand nexium-gui included (54 sources).self/cgen.nx, the C emitter in Nexium: byte-identical tonx emit-con every source in the tree (54, its own 96k-line translation unit included);cargo testdiffs them.- The bootstrap closes:
nx1(the emitter in Nexium, built by the Rust compiler) emits the C of itself,zig ccbuildsnx2from it with no Rust involved, andnx2emits byte-identical C for itself and other programs.cargo testperforms the three stages. tests/spec: the specification's conformance cases, one program per claim SPEC.md makes with its recorded output and exit code, run bycargo testand diffed through every self-hosting stage; sections 2 to 14 (artifacts and the toolchain are covered by the ship tests).self/nx.nx, thenxdriver in Nexium: build, run, test, check, emit-c and tir over the self-hosted pipeline, invoking the C compiler as the Rust driver does; the standard library is embedded in it. It builds itself, and the result builds and runs programs (cargo testchecks that). Nothing past the first compiler needscargo.
Fixed#
Color.Green as u8(a unit enum cast to an integer) emitted a C cast of the whole struct, which the C compiler rejected; it is the tag now.let d: i8 = -128is accepted: a negative literal is one literal, not the negation of 128 (which does not fit an i8).- A labeled block that ends in
break :label valuehas the value's type; it wasnever, and printing the value was rejected. - A
breakinside anorelsedefault, a call argument or any other nested expression now ends awhile true; a non-void function that ended in such a loop was accepted without a return value. - A binary segment sized by an expression (
payload:len*8) is in bits like a constant size; the generated C and the compile-time interpreter scaled it by eight again, so such patterns never matched and such constructions wrote past the intended width. - A
!voidtail expression (amatchwhose arms print, a call) at the end of a function returning!voidis returned; it was reported as unused. %on floats compiles: the generated C applied the integer operator to doubles and the C compiler rejected it; it isfmodnow.- A constant of
List,String,Map,ref classor weak type (or one containing them) is rejected with a hint to store a slice; it used to fail in the C compiler, or worse, be freed by whoever copied it. - Slicing an empty slice (
text[..],"".split(","), an empty binary pattern) added an offset to a null pointer, which is undefined in C: a debug build made by zig 0.14 trapped with "applying zero offset to null pointer". The generated C goes throughnx_padd, which skips the add. if t < 1000 and t > -1000 { t + 1 }: both halves of anandguard narrow the range; the second fact used to replace the first, so the addition was not proven and a!panicsbound on the function failed.- An array indexed by the index of a
for x, i in xsover it is proven in bounds; the proof was skipped for arrays. self/check.nxkept range facts on avaronly until its next assignment was checked, so a guardedt = t - 10was not proven; the facts now hold while the right side is checked, as in the Rust checker.- The generated C no longer collides with macOS's
machheaders, which definets_32and friends as macros: a local namedtsat slot 32 failed to compile on macOS. @cImportretries a preprocessor run that failed without a diagnostic and reports the exit code when it keeps failing, instead of an empty message.self/cimport.nximported hexadecimal float macros (macOS'sMAXFLOAT) that the Rust importer skips, so the two disagreed on<math.h>there; neither imports them now.self/nx.nxlinks native macOS builds with the system compiler, as the Rust driver does (zig 0.14 cannot link against the current Xcode SDK);cargo testbuilds the bootstrap's second stage with that compiler too.- The tree-sitter grammar parses
x orelse return nulland the other jumps afterorelseandcatch, and/little-signedsegment modifiers. List(thread.Worker(Job)): a generic type of another module takes its type arguments from the caller's scope, and is accepted in expression position (List(thread.Worker(Job)).new()). The arguments were looked up in the other module ("cannot find typeJob"), and the expression form was rejected as not a type.
[0.6.1] - 2026-09-19#
Annapurna: Lachenal — patch.
Added#
nx tir: the checked program as S-expressions, the oracle for the self-hosted checker;--sigsprints declarations and signatures only.self/check.nx, the checker in Nexium: module loading, declarations, type interning, signatures (cargo testdiffs--sigsover 41 sources), and function bodies: statements, expressions, calls, builtin methods and namespaces, matches and patterns, casts, coercions, ownership moves and the range analysis. It produces the oracle's exact typed IR for 35 sources, itself among them; generics, closures, trait objects, binary patterns, compile-time calls and C imports remain.
Fixed#
- A C compilation that fails because Zig's shared cache was being written by another
nxat the same time ("failed to check cache") is retried, so parallel builds on Windows no longer fail at random. - The formatter spaces bit-or like the other operators (
a | b, nota| b) and no longer glues-> !List(T) {; closure bars are classified per line so a bit-or inside a closure body is not taken for its closing bar. - A
Stringbuilt for the right side ofand/orproduced C that did not compile (its release was emitted outside the block that declared it). i128range bounds overflowed inside the compiler; ani128literal beyondi64was emitted as undefined C for the minimum;parse_intrejected values above 2^65.- A range fact from an
ifguard (if i == 1 { ... }) outlived its block and could prove a laterxs[i]in range, eliding its bounds check. return xs[i]into a?Tor!Tmoved the element without a clone, and a divergingorelsedefault orcatchhandler marked its operand moved.- Floats print as the shortest text that reads back exactly (
3.141592653589793, not3.1415926535897931). - The tree-sitter parser is regenerated for 0.6.0 (its version is embedded).
[0.6.0] - 2026-09-19#
Annapurna: Camp IV — the syntax settled for 1.0; the parser in Nexium.
Changed#
- Breaking: control flow drops its parentheses and bodies always take braces (decision 87).
if c { },while c { },for x, i in items { },for i in lo..hi step s { },for parallel x in items { }, andif let v = opt { }replaceif (c),while (c),for (items) |x, i|, andif (opt) |v|. The one-statement forms become braced one-liners:if c { return v },let m = if a > b { a } else { b }. A struct literal in a condition needs parentheses (if (Point{ .x = 1 }) == p { }). Match-arm guards arepat if cond =>.catch |e|and closure parameters are unchanged. nx fmtmigrates 0.5 sources to the new syntax as part of formatting;nx fmt --migrate-onlyupgrades the syntax and leaves the layout alone. The migration edits by token span, keeps comments and blank lines, and is idempotent, so running it on a mixed tree is safe.- The formatter puts a space before the body brace of any control head (
if k == Kind.Defer {), where before an uppercase name would have been glued to{as a struct literal. - The language server resolves
forbindings andif letbindings to their declaration. - The tree-sitter grammar, the VS Code and Sublime syntaxes, every example, the standard library, the GUI, the self-hosting sources, and all documentation (including the translations) use the new syntax.
- Moving a
StringorListout of aforloop variable is a compile error (use x.clone()); it silently produced a double free before. A loop variable is a view of the element.
Added#
self/parser.nx: the parser written in Nexium now parses the 0.6 syntax and prints the same tree asnx sexpfor every example, std module, GUI and self-hosting source;cargo testdiffs the two (roadmap phase 4).
Fixed#
unreachableas the last statement of a function returning an error union or struct produced C that did not compile.- A program whose imported module also defines
main(asself/lexer.nxdoes, for running the lexer on its own) took the wrong entry point. nxleft one Zig cache directory behind per process undernx-out/.zig-cache; it is removed when the command finishes, and stale ones are swept.
[0.5.0] - 2026-09-19#
Annapurna: Camp III — other people can build on it: packages, editors, installers.
Added#
- Packages: a
nexium.tomlmanifest with[dependencies]from a git tag ({ git = "...", tag = "..." }) or a directory ({ path = "..." });import deploads the dependency'ssrc/lib.nxandimport dep.moduleitssrc/module.nx; a package's own imports stay inside the package.nx initwrites a manifest,nx addrecords and fetches a dependency,nx fetchclones every git dependency (transitively) intonexium_modules/and writesnexium.lockwith the resolved commits. Seedocs/packages.md. - A tree-sitter grammar (
editors/tree-sitter-nexium) with highlight queries for Neovim, Helix and Zed; it parses every example, std module and self-hosted source without an error node, and CI keeps it that way. - The language server gained go to definition (functions, types, constants, locals, imported and package modules), completion (module members after
alias., fields, methods, variants and errors after., names in scope), and rename (a local within its function, an item across the file). They work from the parsed source, so they answer in files that do not type-check yet. artifact installer:nx shipproduces an installer for a program. On Windows an Inno Setup script (compiled to<Name>-<version>-setup-x64.exewhen Inno Setup 6 is installed) with license page, per-user or all-users install, Start menu entry, optional PATH entry and uninstaller; on Linux and macOS aninstall.shwith--prefixand--uninstallplus a tarball. Listedfilesare copied next to the program.artifact node { name = "pkg" }:nx shipproduces an npm package for an exported library:index.jscalling the shared library through koffi (no build step, no node-gyp),index.d.tstypings,package.json. Slices take typed arrays, arrays or strings; error unions throwNexiumErrorand panics throwNexiumPanicwith the message.
Fixed#
- Parallel builds on Windows could fail inside Zig's own cache ("failed to check cache ... file_open Unexpected") when several
zig ccprocesses started at once; eachnxprocess now gives Zig its own cache under the output directory unlessZIG_LOCAL_CACHE_DIRis set.
[0.4.0] - 2026-09-19#
Annapurna: Camp II — the language talks to the world: sockets, HTTP, threads.
Added#
- Sockets in the runtime:
net.connect,listen,accept,send,recv,close,peer,local,resolve,udp_bind,send_to,recv_from,last_peer; blocking, with per-call timeouts, on Winsock and BSD sockets. New errorsTimeoutandConnectionRefused. std.net:TcpStream(connect with timeout, send, recv, recv_all, peer, bufferedreader()/writer()),TcpListener(bind, accept with timeout, port),UdpSocket(bind, send_to, recv_from),parse_addr,port_of,resolve.std.streamreaders and writers work over sockets.std.http: a client (get,post,requestwith headers; HTTP/1.1, Content-Length and chunked bodies, up to five redirects) and a server (Server.bind,serve,serve_one,Routerwith exact and/*routes,serve_static,Request.param/header, response helperstext,html,json,redirect,not_found). Plainhttp://; TLS is left to@cImport.examples/service.nx: an HTTP service and a client in one program, the phase 2 exit example;examples/errors_more.nxcovers the fixes below.- Threads in the runtime (
thread.start,thread.join,thread.count,sync.mutex_new/lock/unlock/mutex_free,sync.cond_new/wait/signal/broadcast/cond_free) andstd.threadon top:spawnreturns aThread(T, R)whosejoinyields the function's result,runreturns aWorker(T)for functions without one,Channel(T)(send,recv,try_recv,close),Mutex(T)(lockreturns*mut T,unlock). A panic inside a thread is re-raised byjoin. Starting a thread carries thenondeterministicandshared_mutableeffects. ownis accepted on parameters of methods in genericimplblocks.process.exec(argv, stdin, cwd)runs a program with stdin fed, a working directory, and stdout/stderr captured (process.last_stdout,process.last_stderr);std.processwraps it asrun,run_with,shellreturning anOutputwithcode,stdout,stderr.
Fixed#
- Free functions with the same name in two imported modules (
fs.copyandstream.copy) collided in the generated C. - Returning a caught error value (
catch |e| { return e }) from a function returning!Tproduced a bare error id instead of an error union. - An untyped integer literal now coerces into
!T(return 7in a function returning!i32). - The formatter kept the space in
-> http.Response {(a dotted type before a block is not a struct literal).
[0.3.0] - 2026-09-19#
Annapurna: Camp I — the first camp on the mountain: the tools' standard library.
Added#
std.fs:exists,is_file,is_dir,size,modified,read,read_lines,write,append,copy,list(sorted),make_dir,make_dirs,remove,remove_all,rename,walk,cwd,temp_dir,temp_path, and the path helpersjoin,parent,base_name,stem,extension,with_extension,normalize,is_absolute. Under it, newioprimitives in the runtime:append_file,file_kind,file_size,file_modified,make_dir,remove_file,remove_dir,rename,list_dir,cwd,temp_dir; they also work at the REPL.std.time:DateTime(utc,local,with_offset,now_utc,now_local,date,parse_iso,to_ms,weekday,day_of_year,iso,formatwith%Y %m %d %H %M %S %3 %z %a %b %j),Duration(seconds...days,between,since,textsuch as1h 02m),Stopwatchon the monotonic clock,is_leap,days_in_month. Under it,time.utc_offset(ms)in the runtime (0 at the REPL).std.regex: a Pike VM (no backtracking, linear time) with classes,\d \w \s \b, anchors, groups and(?:...), alternation, greedy and lazy repeats including{n,m};compile,find,find_at,find_all,is_match,replace_allwith$1references,split, andMatch.group.- A local that was moved out can be assigned again; the assignment re-initializes it instead of being reported as a use after move.
for (a..b step s) |i|walks a range with a step; a negative step counts down (the loop variable must be signed).while (c) { } else { }runs the else block when the condition turns false, but not after abreak.matchexhaustiveness now uses the full matrix algorithm, so tuples of enums, nested optionals and enum payloads are checked precisely instead of demanding a catch-all arm.nx test FILE --filter NAMEruns the tests whose names contain NAME.std.text: UTF-8 by code point (decode_at,chars,char_count,char_at,slice,truncate,reverse,encode,is_valid), terminalwidth(wide and zero-width aware), and case mapping for ASCII, Latin-1, Latin Extended-A, Greek and Cyrillic (to_upper,to_lower,eq_ignore_case).std.testing:approx,expect_approx,is_err,expect_err,expect_error,expect_contains,expect_lines(names the first differing line) and file snapshots (snapshot,snapshot_in;NX_UPDATE_SNAPSHOTS=1rewrites them).- Embedded std modules can import each other;
erroris a type name (the anonymous error set), andownis accepted on generic parameters. nx testno longer runs the tests of imported std modules.std.stream: bufferedReader(open,stdin,read_line,read,read_all) andWriter(open,append,stdout,stderr,write,write_line,flush,close) pluscopy, over new runtime file handles (io.open,io.read,io.write,io.flush,io.close; handles 1 to 3 are the standard streams).os.environ()lists the environment;args.env_map()turns it into aMap(String, String).nx test --verboseprints timings and the tests a filter skipped;nx doc std.fsandnx test std.regexaccept an embedded module by name.examples/tool.nx: a log scanner (walk a tree, parse timestamps, filter by a date window and a regex, tally by level) in 142 lines, the phase 1 exit example of the roadmap; runs onexamples/data/logsby default.
Fixed#
- A struct, tuple or enum literal that read a local in one field and moved it in a later field saw the already-zeroed value; field values are now materialized in source order.
[0.2.1] - 2026-09-19#
Annapurna: Herzog — patch.
Added#
SPEC.md, the language specification as implemented, andROADMAP.md.nx repl, andnxwith no arguments at a terminal: an interactive session on the compiler's interpreter, with bindings kept across lines, the real diagnostics, I/O, and:load. The Windows installer adds a Start menu entry that opens it, and offers to launch it when setup finishes.- The compile-time interpreter now evaluates
Map, the mutatingList,Stringand slice methods (insert,remove,sort,split,trim,parse_int, ...),expect_eq,assert, UTF-8 helpers and the reference class operations, and pointers stay valid across calls, sostd.jsonand the other std modules run at the prompt. A statement the interpreter cannot evaluate is reported with the position of the failing expression.
Fixed#
- Building a program by its bare file name (
nx run app.nxfrom inside its directory) dropped the C sources declared inartifact link: the empty parent directory produced a lone-Ithat swallowed the next argument. opt.?,opt orelse dandtry reson a local holding an owning value now move the local: it is no longer dropped a second time at scope end (this crashed GUI programs on exit), and a later use is reported as a use after move.
[0.2.0] - 2026-09-19#
Annapurna: Base Camp — where the expedition is staged: the installer, the spec, the roadmap.
Added#
ownparameters:fn f(own s: String)takes ownership of its argument (moved at the call site, dropped or moved on by the callee).orelse return vandcatch |e| return v: jumps on the right oforelseandcatch.String.push_byte(b)appends one raw byte.- Integer and float literals coerce into
?T. examples/own.nxand five compile-fail cases for theownrules.- The standard library in Nexium:
import std.strings,std.lists,std.bytes,std.num,std.json,std.args(96 functions with tests), embedded in the compiler;docs/std.mdlists them;examples/stdlib.nxuses them. - Tuple types as type arguments:
List((A, B)). - A Windows installer (
nexium-<version>-setup-x64.exe): license, overview, per-user or all-users, components (bundled Zig, std and examples, docs, VS Code extension), PATH and.nxfile-type tasks,nx doctoron finish. - An install script for macOS and Linux (
installers/install.sh) that verifies checksums and sets up a C compiler. nx doctor, andnxfinds a Zig bundled next to itself (NX_ZIGtoo).- Releases carry
SHA256SUMS.txt, per-file checksums, and install instructions in the notes; the tarballs include examples, std, and docs. - Recursive types through
List:enum Json { Arr(List(Json)) }. - Matching through a pointer (
match p.*) binds owning payloads by reference, somatch v.* { .Arr(items) => items.append(x) }mutates in place. *Stringand*List(T)coerce to[]u8and[]T;nullcoerces into a nested optional such as!?T.
Changed#
- Moves are tracked per branch: a value moved in one
ifbranch ormatcharm stays usable in the others. - An unbraced
iforelsebody is one statement, soif (c) x = 1works. self/lexer.nxmoves token text withowninstead of cloning it.
Fixed#
- A branch that diverges (
if (c) return x) no longer marks what it moved as moved afterwards. - Two modules each defining a type of the same name (
Parserinstd.jsonandstd.args) collided in the generated C; type names now carry their module. A user function named like a runtime identifier (fn string) no longer collides either. nx fmtspacing after-> List(T), after a closing closure bar, and between anifcondition and a parenthesized body.
[0.1.0] - 2026-09-18#
Annapurna: Miristi Khola — the gorge the 1950 expedition spent weeks finding a way through; the approach.
First public release.
Added#
- The
nxcompiler:build,run,test,check,effects,audit,ship,emit-c,parse. - Language: structs, records with
whereconstraints, enums with payloads,ref classwith reference counting andweak, distinct types, traits with default methods, generic functions and types throughcomptime T: type, closures with explicit captures, error unions withtry/catch,defer/errdefer, optionals, labeled blocks and loops, the pipe operator, binary pattern matching and construction, compile-time evaluation of constants,testblocks. - Effects:
allocates,refcounts,blocks,shared_mutable,nondeterministic,panics,ffi, inferred for every function and enforced against negative bounds, with diagnostics naming the site that introduces an effect. - Standard library builtins:
List,String,Map, slices, formatting,math,io,os,time,random,mem. - Artifacts:
cabi(static library, shared library, C header) andpython(ctypes package and wheel) from one source tree withnx ship;clibinaries. - Embedded mode: reachable mutable globals are rejected when an embeddable artifact is declared (S2); panics never cross the export boundary (S3).
- Examples under
examples/, compile-fail cases undertests/compile_fail/. dyn Traittrait objects with effect bounds,for parallelover a thread pool,using arena { }allocation scopes,comptime test.@cImport("header.h")direct C header import with variadic calls,@cstr, and vendored C throughartifact link { c_sources = [...] }.rustlibartifact: a generated Cargo crate with safe wrappers.- Tools:
nx fmt,nx doc,nx size,nx refcounts,nx leaks,nx lsp. - A conservative region check: returning a view into a local is an error.
process.run(argv) -> !i32,nx tokens, andself/lexer.nx: the first stage of the self-hosted compiler, verified against the Rust lexer incargo test.- Logo and banner under
assets/;docs/architecture.mdexplains how the compiler works. gui/: nexium-gui, an immediate-mode GUI written in Nexium (software rasterizer, bitmap font, buttons, checkboxes, sliders, text fields) over a 200-line C platform layer; Win32 window backend, offscreen rendering everywhere.artifact linkacceptslibs_windows,libs_linux,libs_macos; paths in a link artifact resolve relative to the module that declares it.- Editor support: a VS Code extension (grammar,
nx lspclient, run command, packaged as a.vsixon every release) and a Sublime Text syntax. setup-nexiumGitHub Action and a release workflow template for programs written in Nexium (docs/releasing-your-program.md).linguist/: the prepared GitHub Linguist entry, samples, heuristic, and apply script.@cImport: structs with untranslatable fields are opaque types instead of errors, soFILE*works with Apple's libc.- Native macOS builds use the system
cc(zig 0.14 cannot link against the Xcode 26 SDK);NX_CCselects the C compiler without a flag. - Translations under
docs/i18n/: the README in Spanish, Chinese, Japanese, Korean, French, and German; the language reference and architecture tour in Spanish, Chinese, and Japanese.
[Unreleased]: https://github.com/Londopy/nexium/compare/v1.5.0...HEAD [1.5.0]: https://github.com/Londopy/nexium/compare/v1.4.1...v1.5.0 [1.4.1]: https://github.com/Londopy/nexium/compare/v1.4.0...v1.4.1 [1.4.0]: https://github.com/Londopy/nexium/compare/v1.3.2...v1.4.0 [1.3.2]: https://github.com/Londopy/nexium/compare/v1.3.1...v1.3.2 [1.3.1]: https://github.com/Londopy/nexium/compare/v1.3.0...v1.3.1 [1.3.0]: https://github.com/Londopy/nexium/compare/v1.2.1...v1.3.0 [1.2.1]: https://github.com/Londopy/nexium/compare/v1.2.0...v1.2.1 [1.2.0]: https://github.com/Londopy/nexium/compare/v1.1.0...v1.2.0 [1.1.0]: https://github.com/Londopy/nexium/compare/v1.0.3...v1.1.0 [1.0.3]: https://github.com/Londopy/nexium/compare/v1.0.2...v1.0.3 [1.0.2]: https://github.com/Londopy/nexium/compare/v1.0.1...v1.0.2 [1.0.1]: https://github.com/Londopy/nexium/compare/v1.0.0...v1.0.1 [1.0.0]: https://github.com/Londopy/nexium/compare/v0.9.0...v1.0.0 [0.9.0]: https://github.com/Londopy/nexium/compare/v0.8.0...v0.9.0 [0.8.0]: https://github.com/Londopy/nexium/compare/v0.7.0...v0.8.0 [0.7.0]: https://github.com/Londopy/nexium/compare/v0.6.1...v0.7.0 [0.6.1]: https://github.com/Londopy/nexium/compare/v0.6.0...v0.6.1 [0.6.0]: https://github.com/Londopy/nexium/compare/v0.5.0...v0.6.0 [0.5.0]: https://github.com/Londopy/nexium/compare/v0.4.0...v0.5.0 [0.4.0]: https://github.com/Londopy/nexium/compare/v0.3.0...v0.4.0 [0.3.0]: https://github.com/Londopy/nexium/compare/v0.2.1...v0.3.0 [0.2.1]: https://github.com/Londopy/nexium/compare/v0.2.0...v0.2.1 [0.2.0]: https://github.com/Londopy/nexium/compare/v0.1.0...v0.2.0 [0.1.0]: https://github.com/Londopy/nexium/releases/tag/v0.1.0