# Xo 0.35 release notes


**Downloads:** [Xo 0.35.0 on GitHub](https://github.com/xo-lang/xo/releases/tag/v0.35.0),
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.

## The edit link reuses std's half

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.

