# Xo 0.25 release notes


## 0.25.2: the first binary release

**Downloads:** [Xo 0.25.2 on GitHub](https://github.com/xo-lang/xo/releases/tag/v0.25.2),
with archives for macOS (Apple silicon and Intel, signed and
notarized), Linux (arm64 and x86-64), and Windows (x86-64, not code
signed yet), and a `SHA256SUMS` file.
[Getting started](/docs/getting-started/#install) shows how to install
one.

0.25.2 is the first Xo release with prebuilt binaries. It is 0.25.0 plus
the fixes below. 0.25.1 was tagged but never got binaries: its Windows
`xo.exe` crashed on every build, because Xo's COFF writer overflowed the
16-bit relocation count of a section with 65,535 or more relocations.
0.25.2 writes the extended count, as LLVM's COFF writer does.

Fixed since 0.25.0:

- Windows: sections with 65,535 or more relocations are written
  correctly, so the Windows `xo.exe` builds programs again.
- `json.decode` and `parse_value` reject a byte after the value, as
  RFC 8259 and Go do.
- Map helpers for `Map[K, Bytes]` no longer share a name with the
  `Map[K, Str]` ones, which let a cached function call one with the
  other's result type.
- The arm64 inliner no longer inlines a function into itself after a
  callback brings its own call in.
- RSA: the secret exponent loop no longer allocates, so the
  constant-time checks at clang -O2 find no select in it. No
  secret-dependent select was found, and RSA's output is unchanged.
- Diagnostics: a Set suggests `add` and `remove`; Time and Duration
  methods suggest the operators or `clock.since(t)`; `m[k]?` and
  `m[k] ?? d` suggest `m.get(k)`; `m[k] += v` suggests
  `m.update(k, 0, ...)`.

Known issue: on macOS, `--race` reports show their stack frames without
file and line (fixed on main for the next release).

The notes below are 0.25.0's.

## Introduction

Xo 0.25 makes everyday code cheaper without changing what it means:
callbacks no longer copy what they only read, slicing a string no longer
allocates, and a Mutex release hands off to the writer it woke. It also
adds `r ?? e => ...` for early exits from a Result, lazily computed
constants, any integer type as an index, `--race` with call stacks on
every native target, and `xo vet --cost`, which reports costs the source
does not show.

## Changes to the language

- Any integer type indexes a List or Bytes (reads, assignments, `get`,
  slice bounds; Str slice bounds too)
  ([decision 0331](https://github.com/xo-lang/xo/blob/main/knowledgebase/decisions/0331-any-integer-index.md)).
- `x ?? return v`, `?? break`, `?? continue`, and the Result form
  `r ?? e => body`, which replaces the four line `match` of an early
  error exit
  ([decision 0321](https://github.com/xo-lang/xo/blob/main/knowledgebase/decisions/0321-early-exit-coalesce.md)).
- A top-level `const` may call effect-free functions and is computed
  once, on first use (`const ISO: Layout = time.layout(...)`)
  ([decision 0332](https://github.com/xo-lang/xo/blob/main/knowledgebase/decisions/0332-lazy-const.md)).
- Slices of a Str or Bytes are views of the original, with no copy;
  `clone()` makes an independent copy when a small slice of a large input
  is kept
  ([decision 0330](https://github.com/xo-lang/xo/blob/main/knowledgebase/decisions/0330-zero-copy-bytes-views.md)).
- Evaluation order: a by-value receiver that is a variable or a field
  path is read after the call's arguments, as a `var` receiver already
  was: `t.size(t.add(v))` sees the item `add` pushed
  ([decision 0326](https://github.com/xo-lang/xo/blob/main/knowledgebase/decisions/0326-receiver-read-after-args.md)).
- A function value borrows its arguments: a callback that only reads
  its element no longer counts it, with no new syntax
  ([decision 0337](https://github.com/xo-lang/xo/blob/main/knowledgebase/decisions/0337-borrowed-callback-args.md)).

## Standard library

- `time` layouts are named fields (`"{year}-{month}-{day}"`), checked at
  compile time when constant; `time.layout(text)` gives a `Layout` parsed
  once. With a Layout, native formatting takes 2.6 times fewer
  instructions than with a layout string, and parsing 2.1 times fewer.
- `Str.clone` and `Bytes.clone`. `url.parse_query` and
  `mime.parse_media_type` copy their tokens when the input is over 4 KiB,
  so a kept token no longer keeps a large input alive: 318 MB to 6 MB
  resident in a probe that keeps one token from each of 300 1 MiB inputs.
- A Mutex release yields to the writer it wakes when readers are queued
  behind it. The CRUD server on linux/arm64: 88.6k to 190.8k requests
  per second on 3 cores (2.15x) and 70.5k to 136.0k on 6 (1.93x), p99
  53.8 ms to 15.6 ms and 65.7 ms to 27.0 ms; unchanged on 1 core.
- `std/debug`: a server's CPU profile, heap profile, and scheduler state
  over HTTP behind an auth function
  ([decision 0325](https://github.com/xo-lang/xo/blob/main/knowledgebase/decisions/0325-production-observability.md)).
- `std/metrics` and `std/trace`.
- An idle worker polls the network itself (epoll, kqueue) and runs the
  fibers its events ready, as Go's netpoller does
  ([decision 0324](https://github.com/xo-lang/xo/blob/main/knowledgebase/decisions/0324-worker-netpoll.md)), and a `with_read`
  share of a Mutex no longer counts the value's reference
  ([decision 0334](https://github.com/xo-lang/xo/blob/main/knowledgebase/decisions/0334-mutex-read-share-uncounted.md)).
- ChaCha20 with NEON and the ML-KEM NTT with SSE2.

## Tools

- `xo vet --cost`: notes on costs the source does not show: a loop that
  copies a map's entries first (XO0907), an element counted up and down
  each iteration (XO0908), a list update that copies the list each
  iteration (XO0909), a value over 8 words copied into a closure or
  an Option in a loop (XO0910), and a stored view of a parameter's text
  that keeps the whole input alive (XO0911)
  ([decision 0327](https://github.com/xo-lang/xo/blob/main/knowledgebase/decisions/0327-vet-cost.md)).
- `xo bench --ab`: interleaved A/B rounds of two programs, or of one
  under two settings, with each metric's median, spread, and a verdict
  (proposed design, built; [decision 0340](https://github.com/xo-lang/xo/blob/main/knowledgebase/decisions/0340-xo-bench-ab.md)).
- A code generator failure names the Xo source (`file:line:col: in
  function`).
- `xo prof URL` fetches a running server's `/debug/profile?seconds=N` or
  `/debug/heap` and summarizes it like a file.
- XO0540, a checker warning: a `Mutex.with` closure that writes its
  value and then returns `Err` on another path (the write is undone).
- `--race` reports carry the call stack on every native target, and
  `--race` runs on amd64 (darwin, linux); windows/amd64 builds with it,
  but is not called supported until a Windows run with threads passes
  ([decision 0273](https://github.com/xo-lang/xo/blob/main/knowledgebase/decisions/0273-native-race-prebuilt-tsan.md)).

## Performance

- Callbacks: `xs.any(fn(u) { u.name == want })` over users with two
  string fields takes 34% fewer instructions natively and 27% fewer with
  LLVM; two tasks walking one shared list, 43% and 50% fewer (191 ms to
  75 ms natively).
- Network servers against Go (linux/arm64): TLS 1.3 handshakes 1.2 to 1.5
  times Go's rate; HTTP/2 1.22 to 1.25 times on one stream and 1.63 to
  1.66 on 100. Bulk TLS is behind (0.61 to 0.76 of Go's), and so is a single HTTP/2 256 MiB download (0.61 to 0.67).
- LLVM builds unroll only loops with Float arithmetic, keeping nbody's
  gain (2,308 M to 1,160 M instructions) without 0333's 12.8% more CPU
  for xo's own LLVM build
  ([decision 0338](https://github.com/xo-lang/xo/blob/main/knowledgebase/decisions/0338-llvm-float-loop-unroll.md)).
- Slices that no longer allocate: `mime.parse_media_type` 25.6% fewer
  instructions, `multipart.parse` 16.1%, and `split`, `lines`, and
  `trim` 11 to 14%.
- Fiber stacks no longer take transparent huge pages on Linux: the CRUD
  server at 1024 connections peaked at 2.1 GB of memory before, 47 to 60
  MB after (Go 66 to 78 MB).

