# Xo 0.44 release notes


**Downloads:** [Xo 0.44.0 on GitHub](https://github.com/xo-lang/xo/releases/tag/v0.44.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.
[Getting started](/docs/getting-started/#install) shows how to install
one. These notes cover everything since 0.41.0.

Xo 0.44.0 fixes a native HTTP server on Linux that stalled on pipelined requests whose sender closed its side right after writing them, brings std/sqlite to windows/amd64 with no C compiler needed, adds destructuring assignment (`(a, b) = (b, a)`, struct and list targets), and makes many byte and string paths faster with 32 byte vector scans on arm64 and amd64. std/cli gains Go flag forms for ports of Go tools, and std/sql's drivers carry their effects.

Upgrade if you run a native HTTP server on Linux behind clients or proxies that pipeline requests. No change in this release stops a program that compiled with 0.41.0 from compiling, with one possible exception: code that keeps a `sql.Db` in a closure whose type fixes its effects now names the effect argument (see below).

## Breaking changes

None marked breaking. Changes you may notice:

- **std/sql drivers carry their effects** (decision 0534): `Driver`, `DriverConn`, `DriverRows`, `DriverCursor`, `Db`, `Tx` and `Rows` take an effect parameter `fx`, and `open_driver` returns `Db[fx]`, so std/sqlite's Db performs `fs` rather than claiming `net.dial`. `sql.Db` without the argument stays valid; code that keeps a Db in a closure with fixed effects names it, `sql.Db[()]` for Postgres and the in memory driver, and `xo fix` writes that for you.
- **`xo check` on a directory inside std** (`xo check std/...`) exits 2 with a usage message; `xo test --std-module=M` checks a std module.
- **List targets of a destructuring assignment** (`[a, b] = xs`) report XO0303, as `let`'s list patterns do (they were XO0502).

## Language

- **Destructuring assignment** (decisions 0536 and 0538): `(a, b) = (b, a)`, `(q, r) = divmod(x, y)` and `(p.x, xs[i], _) = t` store a tuple's elements into existing places, and `P{x, y} = p` or `P{x: st.lo, ..} = p` store a struct's fields, the value first, then each place left to right; XO0539 for a place named twice.
- **Effects and interfaces** (decision 0540): a method whose parameter type leaves its effect arguments out (`next: Next`) satisfies an interface method that writes them (`next: Next[fx]`); it was XO0701.

## Fixes

- **A native HTTP server on Linux answers pipelined requests whose sender closed its side right after writing them**; each such connection stalled until its sender gave up. A known issue of the 0.43.0 seed, never in a public release.
- **A type with a method named `debug` or `display` that takes parameters** (a generated API client's `debug(project, zone, ...)`) builds; formatting such a value called that method with only its receiver, which stopped native builds with XO1510, LLVM with a clang type error and Go with a compile error.
- **A stack overflow on Linux and macOS** is reported as "fatal error: stack overflow", as on Windows; on macOS it read as a bus error.
- **Native backends:** a closure that captures more than 32,760 bytes of words builds (a generated GCP client's service); a method of a struct with an effect parameter called through an interface value compiles; `==` on a value holding an `http.Response` or `http.Request` compiles; each was refused before.
- **Go backend:** a struct with no fields and `derive Json`, two types' methods of one name with default arguments, `return` of an if or match mixing `T` values and `Err(e)`, a std module's constant read from user code, and `xs.contains(x)` on a list of a user type all build and run again.
- **Test worlds:** a FakeNet handler's streamed body reaches `Client.send` and `Client.open` natively, and a read or write that waits past its deadline ends with `Timeout` on the Go backend (it reported a deadlock).
- **Cross builds** of a program with a `use c` source to darwin/amd64 from Linux, or to windows/amd64 with no sysroot, stop with a message that says what is needed instead of a C compiler error.
- **Debugging:** lldb on macOS shows a string literal held in a variable correctly in a separately compiled build, and `xo debug`'s `task N` on a task std spawned selects the first frame outside the runtime.
- **std/amqp:** an unsettled `send` with the reader task running no longer waits forever.

- **arm64 programs with more than 128 MB of code** no longer crash in reference counting: a retain or release whose object sat in x16 went through a linker branch island that sets x16 (a generated GCP client's tests stopped with SIGSEGV).
- **Native backends:** a recursive value (a Box) of more than 32,760 bytes of words builds, and structs, tuples and enums of that size compare.
- **Linux with 64 KiB pages:** freeing a 32 KiB heap block no longer discards the pages of the block beside it.

## Standard library

- **std/sqlite on windows/amd64** (decision 0534 stage 2): linked by xo's own PE linker, with prebuilt SQLite objects in the release, so a program using std/sqlite builds with no C compiler; `use c` calls on windows/amd64 use the Win64 convention.
- **std/cli for ports of Go tools:** `single_dash_long: true` reads `-name`, `-name=value` and `-name value` as the long forms (decision 0547), `stop_at_positional: true` ends the flags at the first positional argument as Go's flag package does (decision 0556), and `bool_flag(..., default:)` sets a flag's value when it is absent.
- **`BytesBuilder.write_le(v, n)` and `write_be(v, n)`** append the n (0 to 8) low bytes of a word in one step, on every backend (decision 0561).
- **std/json** `derive Json(match: fold)` decodes object keys as Go's encoding/json does (an exact key first, else the first field whose key folds equal) (decision 0550).
- **std/sql's memory driver** takes `CREATE INDEX` and `DROP INDEX` (PostgreSQL's syntax, one column), so a `WHERE column = value` on an indexed column visits only the matching rows.
- **`use c`:** a C function returning a struct of a const byte pointer and a length binds as a `Bytes` result, NUL bytes kept; a std module's binding can name its handle types in the module's own types.
- **std/amqp and std/amqptest:** desired capabilities on links (Event Hubs v2 geo-replication), an event hub's `georeplication_factor`, and `Broker.attaches()`.
- **For long-running services:** `/debug/sched` reports `heap_chunks` and `heap_cache_bytes`; heap profiles carry Go's `bytes` label, so `go tool pprof -tagfocus=192B` keeps one size.

## Tools

- **Windows version information, icons and a manifest** (decision 0560): `xo build --versioninfo=FILE.json` reads goversioninfo's JSON, `--icon=FILE.ico` and `--manifest=FILE` set the icons and manifest, so a project moving from goversioninfo keeps its versioninfo.json.
- **LLVM builds of a function with more than 8,000 blocks** on arm64 compile in about a minute instead of more than 10 (LLVM's AArch64 machine combiner is turned off for that module).

## Speed and memory

- **32 byte vector scans** (arm64 NEON, x86_64 SSE2) in std's byte paths, on linux/arm64 against the release before each step: a one byte `find` 0.68, Str and Bytes `==` over 16 bytes 0.73, `Str.count` of a newline 0.741, `find` of a common first byte 0.347, `Str.quote` 0.542, `equal_fold` on 4 KiB 0.185 (0.165 of Go's).
- **Crypto on arm64 and amd64 vector units:** SHA-256 0.884 of before on linux/arm64 (0.79 on linux/amd64), SHA-1 0.884 (0.86), SHA-512 0.965.
- **Encoders:** std/cbor's deterministic marshal of a derived struct 0.442 of before, and `cbor.marshal` of a derived type writes its encoding directly, 0.46 of that (2.8 ms against Go's 4.3 ms), `JsonWriter`'s strings 0.75, JSON decode of 10 KB documents 0.827 wall and 0.785 max RSS on linux/arm64, std/sql's memory driver ORDER BY 0.574, labelled std/metrics counters 0.760 (a small List literal argument no longer allocates).
- **Memory:** a List or Str growing past 1 MiB reuses its last mapping (JSON decode on linux/amd64: max RSS 45,352 to 12,092 KB, Go 13.9 MB); a List of word-sized elements starts with room for 2, so xo's own build peaks 4.4% lower.
- **Toolchain:** a warm build on Linux no longer copies its part objects (an unchanged hello wrote 3.7 MB to the cache every build); the native backends' self build peaks at 6.2 to 6.4 GB mapped instead of 6.8 to 6.9 on linux/amd64; std's parses come from the build cache.
- **More std paths:** std/path's `dir`, `base`, `ext` and `split` 0.918 of before (`clean` 0.934, `join` 0.942), an AES key schedule 0.717, std/netip's IPv4 parse 0.816, on linux/arm64.
- **Long-running services on Linux** give the system the pages of free heap blocks of 16 KiB and over once they stay free 30 to 60 s (decision 0533).
- **Go backend builds** of large generated packages take less memory: azure armnetwork compiles in 8.1 GB and 61 s instead of 9.1 GB and 68 s.

## Known issues

- On macOS, once std's prebuilt variants are in the cache, a program's default build compiles separately and its debug map names the object files lldb reads in the build cache (`~/Library/Caches/xo/backend/dbgobj/`), so the binary carries the builder's home directory despite `--trimpath`. Debugging works, but a binary you hand to someone else names your home directory. Build with `XO_SEPCOMP=0` (or `--release`) for a binary to share. Decision 0563 settles it. The release's own binaries are built whole and hold no build path.

## Xo against Go

Measured before the cut on the release candidate (b0ee4622c, Go 1.26.1):

- **macOS arm64 programs**: ahead of Go on 15 of 20 run rows by wall (an interpreter 0.31 of Go's time, collection iteration 0.27, a mutex 0.30, spawn 0.46), level on four, one slower (nbody 1.026); every row at 0.32 to 0.84 of Go's peak memory.
- **Tests of std** (`xo test --std-module` against `go test`): json cold 0.395 of Go's time and 0.271 after an edit; base64 0.671 and 0.705. After an edit xo's test run peaks higher (base64 1.68 times Go's memory, json 1.22).
- **Builds**: cold 1.04 to 1.41 s against Go's 1.44 to 1.51 s, an edit 40 to 100 ms against 100 to 110 ms; binaries 1.45 to 1.63 MB against 2.39 to 2.43.
- **HTTP server** (linux/arm64): `/hello` serves 1.29 times Go's requests per second at 32 connections and 1.83 times at 256; a JSON response (`/alloc`) 5.99 and 3.18 times, and at a fixed 20,000 requests/s its p99 is 0.22 of Go's. Every row is level with 0.41.0.
- **linux/amd64** (micro13, `--release`, 9 rounds): ahead of Go on six of seven programs (JSON 0.29 of Go's time, string building 0.40, map1M 0.69, fib 0.92, both sorts 0.94), spawn100k level; JSON's peak memory is now 0.70 of Go's (0.21 of 0.41.0's), map1M's 1.42 times. Level with 0.41.0 on every row but JSON (0.725), within 2%.

