Xo is experimental. The source code is not public yet; until then, try Xo in your browser.

Xo v0.9.0

2026-10-10

Spec draft 0.9.

  • Module proxies (decision 0126): XO_PROXY lists proxy URLs, direct (git), and off, with Go’s fallback rules (comma after 404/410, pipe after any error; default direct), and XO_NOPROXY patterns go to git. The read only protocol serves @v/list, .mod, .zip, and .lock per module; xo mod proxy serve [--fetch] [<cache dir>] serves a module cache and can fill it from git, as a team cache. Proxied trees are checked against xo.lock exactly as git fetches are.

  • Checksum log (decision 0124): with XO_SUMDB=<name>+<id>+<key> [<url>], the first fetch of every module version must match the log’s record, be in its signed Merkle tree, and extend every tree the module cache saw before; otherwise XO1313 and nothing is cached. An unreachable log or malformed settings is XO1314; XO_NOSUMDB keeps private modules out. The default stays off (no public log yet).

  • Definition hashes (decision 0125, the last step of 0086): xo query hash <name> prints a content hash per top level definition that survives renaming locals and changes with what the definition, or one it calls in the module, computes. xo publish --check lists the definitions (private ones too) changed since the last tag, including those changed only through a helper, next to the API comparison.

  • std/http speaks HTTP/2 over TLS on every backend (decision 0121): with_tls servers and https clients agree on h2 by ALPN, plain http stays HTTP/1.1, and nothing in the API changes. Requests of a connection run at once, each as a task; a fault in a streamed body resets only its stream. The native backends implement it in C (frames, HPACK, flow control) with net/http’s limits (250 streams, the header list limit with 431, the rapid reset limit) and its error texts, and their client keeps HTTP/2 connections for later requests as Go’s does. Over one connection the native server does 87.8k requests per second in the HTTP benchmark, the Go backend 58.2k (bench/results/http2-2026-10-10.md).

  • Native test binaries (xo test --no-gc, decision 0120): property tests and testing.gen work, with the Go backend’s generated values for a seed, the same failing case, and the same shrunk input in the report. A Set or Map keyed by a sized integer (Set[Int8]) now builds natively. xo test --real gives native tests real capabilities for the effects they declare (fs, fs.read, fs.write, net, clock, env, stdio, proc), as on the Go backend.

  • Native backends: deferred code that faults no longer skips the rest of the cleanup. The deferred code registered before it and that of the blocks around it runs, the last fault is reported, as on the Go backend (core 3.5, decision 0085 amendment), and a task that caught such a fault with task.isolate sees cancellation again (it could deadlock before).

  • LLVM backend (decision 0119): generated code is compiled for the same CPU and features as the C runtime (clang’s defaults): LSE atomics for shared reference counts on darwin/arm64, outline atomics (LSE when the CPU has it) on Linux arm64. HTTP benchmark +3.5 percent.

  • Native Mutex (decision 0118): a writer’s release hands the lock to the readers waiting, so they no longer wake only to park again: a contended readers and writer load (bench/perf/conc rwmutex, new) went from 967 to 131 ms and from 6.3 to 0.25 s of CPU. Long lived scopes (the native HTTP server’s connections) no longer keep every finished task until they close, and the native allocator moves freed blocks between threads, so a server’s memory stays flat (28 MB instead of 295 MB after 300k TLS connections).

  • Native HTTPS (decision 0117): full TLS handshakes about 1.6 times as fast and at half the CPU (9.7k to 15k per second in the HTTP benchmark, Go backend 17k): X25519 key shares through HACL*, faster P-256 inverses, a spinning lock for Mbed TLS’s key store, SHA instructions. The native server now sends session tickets as Go’s does (TLS 1.3 resumption with ECDHE, TLS 1.2 tickets; keys rotate daily), so returning clients skip the certificate signature. bench/perf/http’s loadgen has -resume (RESUME=1 in run.sh).

  • xo lsp (decision 0114): a Language Server Protocol server with diagnostics and their fixes as quick fixes, hover (signature, doc, or type), definition (into std too), references, rename, document symbols, semantic tokens, and formatting. The VS Code extension starts it; ```xo fences in Markdown are highlighted.

  • VS Code syntax highlighting (decision 0113): editors/vscode/ registers .xo files with a TextMate grammar covering spec section 1 (keywords, contextual words, interpolation, raw and bytes strings, durations, sets, effects in uses). Install by symlinking it into ~/.vscode/extensions.

  • use c "<header>" [link "<lib>"]... [as name] [uses ...] on the native backends (decisions 0093 stage 2 and 0116): bindings generated from the header with clang (xo bind --c), scalars, Str, Bytes with a length, flat structs, and opaque handles at the boundary, the ffi effect unless the line narrows it, XO1104/XO1105 for headers and names that do not bind, XO1201 on the Go backend. Calls are not recorded yet (native builds do not record).

  • Decision 0111: passing one variable, or overlapping places (x and x.f, xs[i] and xs[j] unless both indexes are different literals, a var receiver and an argument naming it), to two var parameters of one call is XO0503 on every backend; the native backends no longer report XO1201 for it.

  • Decision 0112: a value that holds a handle (Mutex, Atomic, Chan, Task, Scope, Listener, Conn, task.Group, the std/http and std/sql handles, the test fakes) or a function value is not comparable: ==/!=, Map keys, Set elements, derive Eq/Hash, and T: Eq/Hash arguments are XO0404 with a note naming the field that holds it; derive Ord already was. The checker now looks into struct fields and enum payloads for Eq (it looked only at type arguments).

  • Native backends (decisions 0107 to 0109): Proc.run, os.tasks (a scope around main and around each test), method values (let f = x.method), map_concurrent_until, base64, Range.to_list, Rand.shuffle, target (a compile time constant), and in native test binaries MemFs (every method, fail_next, case sensitivity, files), FakeProc run and on_run, os.proc.exit inside testing.run_main, and run_main of a main that cannot fail. Fixed natively: a receive arm after an after arm lost its value; a ? in a closure printed the function’s frame name without .closure, and one in a Mutex.with closure none; FakeClock.advance returned before the tasks it woke ran; the success type of Err(e)? was XO1201. One binding passed to two var parameters is now XO1201 natively (the Go backend shares it, the native backends copied it).

  • TestCompileProperty (decision 0110): random checker valid programs over the optional syntax must build on the Go backend and with --no-gc, and run alike.

  • Fixes from Codex r3: a select arm that receives without binding the value no longer generates Go that fails to compile; the “propagate with ?” fix is offered only where ? can propagate; help notes for XO0205 in closures and for passing TestOs where Os is expected.

  • Native backends (--no-gc, LLVM and arm64): interface types as values (decision 0100): dynamic dispatch, adapters for methods that cannot fail, error conversion, interface to interface conversion, Display and Debug of the dynamic value, and ==; x.debug() on any value.

  • Native backends: newtypes (Email("x"), e.0 read and assigned, Email(p) patterns, Debug, ==, Map keys) and list patterns ([], [a, .., z]). Fixed: an or pattern whose alternatives bind the same name (Leaf(v) | Node(left: v, ..)) read the first alternative’s binding natively, a stale value when another alternative matched.

  • Native backends (decision 0101): Float32, Secret[T] redacted in every output path, Buf[T], < on derive Ord types, Int32.try and the other sized trys, Int.pow, clamp, min/max on Str, Set union, intersect, difference, Map.filter, List.insert, remove_at, indexed(), xs + ys, std defaults (crypto.token(r), Chan.new()), get_or_fault on any key type, builtin collections through interfaces, shorthand field patterns. == on a value holding a function or runtime handle outside an interface is now XO1201 natively (it failed in code generation). New test TestGenGoldensNative runs 66 internal/gen goldens on Go, LLVM, and arm64 and compares them.

  • Native backends: std/path (std/native/path.xo), task.semaphore, task.once (a fault in its function is not stored natively: the next caller runs it again), and task.map_concurrent (std/native/task.xo); Debug text of Result[T, Never] values. Fixed: natively, a task whose value is a Result failed its scope when that value was an Err; shorthand struct patterns Bag{items}.

  • Native backends (decision 0102): every Fs method and attenuation (sub, cwd, read_only, write_only, escapes Denied), Stdio.write, and standard input reads (read_line, read_all, lines) that a cancellation or a stop request ends with IoErr.Canceled. Fixed natively: task.with_timeout returns TimedOut when its closure finishes after the deadline without a cancellation point, as on the Go backend.

  • Native backends: error frames (decision 0104): each ? records the function, location, and parameters lazily, and main’s error report prints at ... lines identical to the Go backend’s.

  • Native Fs and standard input code is compiled into a program only when it calls them (XO_FS, XO_STDIN; hello world unchanged). Fixed: hybrid builds (--hybrid) declared a Bool result’s argument size rounded up (go vet -asmdecl).

  • xo test --no-gc: FakeStdio.set_input feeds the reads; fixed a hang after FakeProc.signal_after fired (the virtual clock stopped moving).

  • Native backends: all of std/time (decision 0094; std/native/time.xo and std/llrt/xo_time.c, a port of Go’s TZif reader and zone lookup): zones with the same per use embedded data (or --tzdata=system), civil time, gaps and overlaps, layouts, strict parsing with the same error texts and faults; also Duration.from_secs, scale, ceil_seconds, Time.parse_rfc3339, and Time.UNIX_EPOCH as a value. Programs that do not use std/time do not grow. TestTimeDiff compares about 70,000 lines with the Go backend. Fixed: the arm64 backend failed to assemble functions over 8192 instructions (tbz range).

  • HTTPS in std/http on every backend (std.md 4.6, decisions 0105 and 0106): http.tls_cert(cert_pem, key_pem), Server.with_tls(certs) (SNI picks among the certificates), Client.with_roots(pem), and TlsErr. The native backends (--no-gc) speak TLS 1.2 and 1.3 through Mbed TLS 4.1.1 (vendored in third_party/mbedtls, built once per compiler into the user cache, linked only into programs that use std/http), verify against the system’s roots on macOS and Linux, and report Go’s error texts; an https URL is no longer XO1201 there. std/http speaks HTTP/1.1 only, also over TLS: the Go backend’s client no longer negotiates HTTP/2. Failed TLS handshakes are not logged.

  • bench/perf/http/run.sh: SCHEME=https serves the Xo configs over TLS and NEWCONN=1 opens a connection (and a handshake) per request.