Xo is experimental. The source is now public on GitHub, or try Xo in your browser.

Xo 0.35 release notes

released 2026-10-30 · tag v0.35.0 · all releases

Downloads: Xo 0.35.0 on GitHub, with archives for macOS (Apple silicon and Intel, signed and notarized), Linux (arm64 and x86-64), and Windows (x86-64, Authenticode signed), and a SHA256SUMS file.

Xo 0.35.0 makes the edit link reuse the standard library’s half: a separately compiled build keeps std’s laid-out code from the last link and links only your program against it, so the HTTP server’s edit link on macOS arm64 takes 9.89 ms instead of 23.68, and on Linux arm64 13.84 ms instead of 39.07. use go now reaches real Go libraries: Go generics, Go interfaces implemented by Xo functions, callbacks that return errors, pointers to basic types, channels and functional options all bind, and chi routing, zap logging and an AWS SDK v2 S3 put and get run in a tracked workflows suite. It also adds std/x509, authenticated encryption, html/template’s contextual escaping, DNS record lookups, rate limiting and backoff, fixes a silent miscompile (two same-named modules shared one function body), and fixes the Windows tester’s use go bugs 18 to 20. Archives for darwin/arm64 and darwin/amd64 (Developer ID signed and notarized), linux/arm64, linux/amd64 and windows/amd64 (Authenticode signed), each xo stamped with the commit it was built from (xo version -v).

Six changes break source, all additions to interfaces or types of the prelude and std/json: a user type implementing Net, Proc or Env adds the new methods, code destructuring FileInfo field by field adds the field, and an exhaustive match on json.Value adds an arm. The list is below.

Breaking changes

Interfaces have no default bodies, so a user type that implements one of these interfaces adds the new methods; a wrapper forwards them to its inner value.

  • Net gains dial_timeout and lookup_host (decision 0406).
  • Proc gains notify, stop_notify and request_stop (decision 0407), signal delivery as Go’s signal.Notify.
  • Proc gains pid and ppid (decision 0410).
  • Env gains hostname() (decision 0410).
  • json.Value gains a variant for trusted template content (decision 0408, for html/template’s trusted types below): an exhaustive match on json.Value needs a new arm, which xo fix adds as a hole.
  • FileInfo gains is_symlink: Bool (default false): read_dir and walk report a link as itself, stat follows it. A pattern that destructures FileInfo field by field adds the field; struct literals keep working.

0.34.0 compiled only your code on an edit; 0.35.0 also links only your code. The first separately compiled link keeps std’s half (its code, read only data and DWARF already laid out and relocated) in the back-end cache, and later links place the program after it. XO_STD_HALF=off turns it off.

  • macOS arm64 (decision 0409): the HTTP server’s edit link 23.68 to 9.89 ms, fib’s 9.40 to 3.31 ms. The program’s code is in its own section (__TEXT,_ptext) after std’s; lldb, dsymutil, codesign, Xo tracebacks and profiles read it as before.
  • Linux (decision 0420): the HTTP server’s edit link 39.07 to 13.84 ms on linux/arm64 against 0.34’s link; on linux/amd64, where x86-64 had no split layout before, 60.10 to 39.27 ms against the same layout without the half. An earlier step of the same decision (no second walk of std’s objects) took linux/arm64 from 48.61 to 38.31 ms.
  • windows/amd64 takes the first step: std’s sections come first and keep their addresses and bytes whatever the program grows or imports; the program’s own sections follow (decision 0409’s PE port). The half itself on Windows is not in 0.35.
  • Smaller macOS binaries: an edit build no longer embeds std’s DWARF; tracebacks read a compact __XODW segment, and lldb and dsymutil read std’s full DWARF through a debug map into the back-end cache (decision 0401). A small program’s binary 2.03 to 1.32 MB, link time unchanged. A binary copied elsewhere after the cache was pruned still prints tracebacks with lines; dsymutil ./prog keeps std’s names and variables for good.
  • A build that rebuilt nothing hits the binary cache again (a cold build’s spill files no longer change the link’s cache key).
  • A failed std store build is no longer silent: each separately compiled build prints one warning with the std file:line and builds the whole program.

use go reaches real Go libraries

  • Go generics (decision 0415): generic Go functions and generic Go types bind as Xo generics.
  • Xo implements Go interfaces (decision 0416): Xo functions implement a Go interface through a constructor with named callback parameters, and Go callbacks that return an error bind.
  • Go’s any (decision 0412): a parameter or result of any (and ...any, []any) binds as go.Any.
  • Pointers to basic types (decision 0419) bind as T?, a copy at the boundary.
  • Channels (decision 0421): a receive-only Go channel, or one with both directions, given to Xo is a go.Recv[T].
  • Functional options (decision 0422): a variadic of an unnamed Go function type (...func(*s3.Options)) binds as a List of Xo functions, so AWS SDK v2’s s3.new_from_config and every s3 client operation bind.
  • Also: Go error outside a final result binds as go.Error?; handles convert to an interface their Go type satisfies (the as_ upcasts cover other packages’ interfaces too); Go function values, struct fields and package variables of function type, methods of named basic types, nested Go modules (aws-sdk-go-v2’s) and callbacks with several results bind; go.uber.org/zap binds again.
  • A tracked Go workflows suite runs real library workflows end to end: chi routing, zap logging and an AWS SDK v2 S3 put and get pass; pgx, toml and yaml are expected failures, each with its cause recorded.
  • Coverage of the Windows tester’s survey set (47 Go std packages, 40 third-party modules): third-party functions and methods bound 98.4 to 98.5% after the channel step, std 90.5%; before decision 0412 (the binder with bugs 18 to 20 fixed, binding format 8) it bound std 78.8% and third party 76.9% of the same set.

Bindings regenerate automatically on the first build with 0.35.0.

Standard library

  • std/x509 (new): parse and verify certificates as Go’s crypto/x509 (names, validity, keys, SANs, constraints, usages, every extension; verify with Go’s error texts; verify_hostname, system_roots), and create certificates, requests and CRL parsing (create_certificate, self_signed for development, create_request); Ed25519 and RSA certificates and requests are byte for byte Go’s. Against Go: parse_pem of a three certificate chain 1.183 of Go’s time, verify 1.002. std/tls gains peer_certificates().
  • std/crypto: authenticated encryption (Aead AES-GCM and ChaCha20-Poly1305, seal_random, open_random, explicit-nonce seal and open) and HKDF over any Hash, checked against Go’s tests and Wycheproof; Ed25519 key generation, signing and verification on Go’s code: signing from an expanded key 0.65 of Go’s time, from the seed 1.185 (was 1.812); ed25519_sign also takes Go’s 64 byte private key; PKCS #8 marshal_private_key.
  • std/template: Go’s html/template as the html mode (decision 0408): parse_html escapes every action for its HTML, attribute, URL, JavaScript or CSS context; template.html, css, js, jsstr, url, attr, srcset mark trusted content; custom functions through parse(funcs:). New std/html: escape_string, unescape_string.
  • std/dns: MX, TXT, SRV and reverse (PTR) lookups (decision 0417), with Resolver{server} to ask one server, native on Linux and macOS and, with Xo’s own client, on Windows (decision 0427). Native lookups cache resolv.conf and hosts as Go does: an MX lookup 0.637 of its former instructions, 1.004x Go’s.
  • std/rate and std/backoff (new): golang.org/x/time/rate’s token bucket and cenkalti/backoff’s exponential backoff with retry, both on the Clock they are given, so tests run on the virtual clock.
  • std/crc32 and std/fnv (new): Go’s hash/crc32 (IEEE and Castagnoli) and hash/fnv. CRC-32 runs at Go’s speed: on x86-64 IEEE folds with PCLMULQDQ, 0.976x Go (was 8.084x), Castagnoli 0.846x Go (was 1.743x); on arm64 Castagnoli uses CRC32CX.
  • Files: walk_dir(fs, dir, visit) with WalkStep (Continue, SkipDir, Stop), Go’s filepath.WalkDir over any Fs; write_atomic(fs, path, data); create_dir_all_mode.
  • Also: toml.encode(v); std/cli float_flag; log.with (decision 0413); time truncate, round and a task-free Ticker (decision 0414).

Compiler and runtime

  • The compiler uses less memory building itself: the back-end cache’s pack segments hold one block per entry and are written as each part finishes (decision 0418): the cold build’s footprint with the cache on 10428 to 8371 MB, and the second build’s CPU 21.4 to 9.7 s. The native back end’s parts own their SSA bodies and drop them as each is emitted (decision 0424): heap peak 10764 to 9837 MB, footprint 7667 to 7287 MB; its output is the same.
  • Profile-guided code layout, stage 1 (decision 0403): xo prof FILE --layout=xo.layout turns a CPU profile into a layout file, and xo build orders the program’s functions by it when the module root has xo.layout (--layout=FILE, --layout=off). Nothing changes for a program without one.

Fixes

  • On Linux the build cache hits again: an unchanged xo build, xo run or xo test no longer builds again (the build recorded /proc/meminfo, whose contents change on every read, as an input; every release since 0.28.0 had it).
  • Silent miscompile: two program modules whose paths end in the same name (app/a/util, app/b/util) with a same-named function called one body for both on the native and wasm backends (LLVM failed to build). Modules are now named by their full path.
  • use go, from the Windows tester (bugs 18 to 20): a Go function that writes into a []byte argument (Reader.read) lost the data; a function with a parameter of an unexported type broke its whole package’s binding; bindings made one at a time missed indirect requires.
  • WebAssembly: a program using scope, spawn or Chan failed to build with --backend=wasm since 0.27.0 (fixed, with a guard against counting such a failure as unsupported); converting a UInt64 to Float32 failed to assemble since 0.32.0.
  • Linux Chan[Int] use-after-free: a word channel’s handle could be released before the channel call, found by the race tests on Linux.
  • url.scheme is now "https" for requests a TLS server receives, on every backend and over HTTP/1.1 and HTTP/2 (it was empty before; Go leaves it empty).
  • linux/arm64 through a stock cc (GNU as, or clang with its default processor) failed on crc32x in 0.34.0.
  • Native windows/amd64: a fault (an overflow, an index out of range) reaches task.isolate and prints a full traceback instead of exiting 70; Env.hostname, Proc.ppid, UDP truncation, directory symlinks and DNS lookups work; fsync of a read only handle succeeds as it does on Linux and macOS; backslash and UNC paths in flags and path variables are read correctly; the PDB GUID comes from the objects’ content, so identical builds are identical.

Xo against Go

The release comparison runs the same programs in Go and in Xo on the release’s own build (the v0.35.0 darwin/arm64 archive, Go 1.26.1, darwin/arm64 on an Apple M4 Pro, xo bench --ab, 10 interleaved rounds, quiet machine). Ratios are Xo’s wall time over Go’s; under 1.0 means Xo took less.

  • Compute: interpreter 0.321, strings 0.460, sort 0.805, collatz 0.898 (was 0.965), sieve 0.946, fib 0.965; n-body 1.059, still slightly slower than Go.
  • Collections: branch 0.025, iter 0.274, putget 0.595, get 0.618, update 0.616, copymut 0.632 (was 0.675), push 0.731.
  • Concurrency: mutex 0.279, spawn 0.426, fanout 0.687, ping-pong 0.873, rwmutex 0.901; parallel map 0.988 (was 1.010), now level with Go.
  • Against 0.34.0: 18 of the 20 workloads are faster than Go; n-body is slower and the parallel map is within noise. The interpreter’s ratio moved from 0.291 to 0.321, but building it with the 0.34.0 and 0.35.0 releases and timing the two directly gives 1.028 (0.953 to 1.096), within noise.
  • Builds: an edit rebuilds in 60 to 70 ms (the collections program 130) against go build’s 100 to 110, and a cold build takes 880 to 1100 ms against Go’s 1430 to 1520. An unchanged rebuild is served by the run cache in about 10 ms.

Platform notes

  • macOS arm64 is the measured platform unless a line says otherwise; the linux/amd64 and Windows timings are from the owner’s offload hosts.
  • std’s half on the edit link is on for darwin/arm64 and Linux (ELF); windows/amd64 has the section order only. --release, --race, a cc link and programs that use c build the whole program as before.
  • The debug map (smaller binaries, std’s DWARF in the cache) is macOS only.
  • Native Windows DNS asks the adapters’ DNS servers directly, as Go’s pure Go resolver does, so Windows’ NRPT and DNS over HTTPS settings are not consulted.

Known issues

  • --race with --backend=llvm on Linux can report a spurious data race inside the runtime’s task free for a program that sends on a channel from a spawned task. The default native backend’s --race is not affected.