Xo and Rust
On this page
Part of the comparison series that starts with Xo and Go. It is not a “which is better” page. Rust and Xo both reject whole classes of bugs at compile time and both compile to native code without a garbage collector, but they make different trades: Rust gives the programmer control over memory and aliasing, Xo takes that control away in exchange for a smaller language. Rust is also mature and production proven; Xo is experimental. This page says where each is the better choice and what the measurements show, including the many rows where Rust is faster.
Versions. Claims about Rust are checked against Rust’s own documentation (the book, the reference, the standard library and Cargo docs) as of Rust 1.99.1 (the newest release, 2026-10-15), which is also the Rust the measurements used. The Xo in the measurements is a build of main between 0.26.0 and 0.27.0 (it reports itself as 0.26.0); they will be rerun with the released 0.27.0. Last verified: 2026-10-23.
Short version
Choose Rust when you need the fastest code for compute and floating point work, the smallest memory footprint, control over allocation and layout, no runtime at all (embedded, kernels, libraries called from C), many targets, a large ecosystem (crates.io, tokio, serde), or a stable language with a long compatibility record. In the measurements below Rust was faster on most compute rows, on JSON, on locks, used a quarter of Xo’s memory as a web server, and made binaries about half the size of Xo’s release binaries.
Consider Xo when you want much of the same compile-time safety (no null, no unhandled errors, exhaustive matches, no data races) without lifetimes, borrowing rules, or choosing an async runtime; when you want a dependency’s capabilities limited and recorded; when integer overflow should fault in every build; and when much of the code is written by AI agents working from structured compiler feedback. As a web server Xo served more requests per second than axum on tokio in the measurements below, with lower tail latency, but used more CPU per request.
What each is for
- Rust: “A language empowering everyone to build reliable and efficient software”, “with no runtime or garbage collector”, whose “type system and ownership model guarantee memory-safety and thread-safety” (rust-lang.org).
- Xo: backends, tools, and web services, designed so AI agents can write, check, cross compile, and debug programs with fast, structured feedback (Why Xo?).
The language
| Rust | Xo | |
|---|---|---|
| Absent values | No null; Option<T> | No null; T? is Option[T], and the compiler makes you handle None (core 2.5) |
| Errors | Result<T, E> and ?. Result is #[must_use]: ignoring one is a warning by default, not an error (std::result) | T ! E; an unhandled Result does not compile; ? propagates and records an error trace (core 2.6, 11.1) |
| Sum types | Enums with data and exhaustive match | Enums with payloads and exhaustive match, with guards (core 2, 3) |
| Generics | Generics with trait bounds, monomorphized | Generics with constraints (core 2.7) |
| Values and aliasing | Ownership and borrowing: one mutable reference or many shared ones, checked by the borrow checker, with lifetimes in signatures where needed | Value semantics with copy-on-write; no references to annotate; mutation is visible in signatures (var parameters) (core 5) |
| Concurrency | Threads in std; async/await in the language, but “Most languages that support async bundle a runtime, but Rust does not” (the book, 17.1); tokio is the usual choice | Tasks in the language with one built-in scheduler; structured concurrency (scope), cancellation without a parameter, channels and select (core 7) |
| Side effects | Not in signatures | Effects in signatures (uses (net, fs)), capability values passed from main, and fakes for tests (core 6) |
| Escape hatch | unsafe blocks for raw pointers, FFI and other operations the compiler cannot check | No raw pointers in Xo code; C and Go code through use c and use go, which xo audit lists (Dependencies) |
Memory
- Rust frees memory when its owner goes out of scope, with no
collector. Shared ownership uses
RcorArc(reference counting), and “Rust allows memory leaks by usingRc<T>andRefCell<T>”: a cycle is never freed (the book, 15.6). - Xo native builds use reference counting for every heap value: a value is freed when its last reference goes, with no collector pauses. Value semantics leave no cycles to leak. HTTP handlers get request scoped arenas by default (FAQ, core 10).
- The difference in practice: Rust moves and borrows without touching a
count, so most Rust code pays nothing; Xo pays for counts as values
move.
xo vet --costpoints at the loops where that, or a copy-on-write copy, is expensive. In the measurements below Rust’s programs used less memory on most rows (Xo’s runtime and scheduler start larger), and Xo’s used less on the rows that build large collections (sieve, sort, strings).
Safety and security
| Rust | Xo | |
|---|---|---|
| Memory safety | Safe Rust is memory safe; unsafe code can break it, and the guarantee depends on every unsafe block in the program and its dependencies being correct | Memory safe without an unsafe keyword; C reached through use c is outside the guarantee and listed by xo audit |
| Data races | “Safe Rust guarantees an absence of data races”, but “Rust does not prevent general race conditions” (deadlocks, wrong ordering) (Rustonomicon) | Values never alias mutably, so data races are impossible except through Mutex and Atomic, which are synchronized (core 5); race conditions are possible too, and xo test --explore=N runs N seeded schedules to find them |
| Integer overflow | Panics in debug builds; in release builds (--release) “Rust performs two’s complement wrapping” unless overflow-checks is set (the book, 3.2) | Faults in every build: a bug report, not a wrong number (core 2, 8); wrapping is explicit (wrapping_add, std 2) |
| Capabilities | Any crate can open files, sockets, or processes; a build script (build.rs) is compiled and run “just before building the package” with the user’s permissions (Cargo book) | Code reaches the file system, network, environment, or processes only through capability values it was given; a module can be held to a ceiling (uses none); no dependency code runs at fetch or build setup: no build scripts, no install hooks (core 6, decision 0156) |
| Supply chain | Cargo.lock pins versions and records checksums; the RustSec advisory database and cargo audit report known vulnerabilities | xo.lock hashes each module version, plus an effect lock: an update that adds an effect fails until accepted, and xo audit lists each dependency’s effects, escape hatches, and vouches (Dependencies). There is no vulnerability database yet |
Compiler and toolchain
Builds and binaries (the CRUD server of the benchmarks below and a
hello program; Rust with cargo build --release and the twins’ profile,
as the measured binaries were built; Go for reference; one build each,
Apple M4 Pro, 2026-10-24):
| Rust 1.99.1 | Go 1.26.1 | xo 0.29.0 | |
|---|---|---|---|
| Cold build, CRUD server | 7.6 s | 2.9 s | 2.3 s |
| Cold build, hello | 1.3 s | 1.4 s | 1.1 s |
| Warm build, nothing changed (CRUD / hello) | 0.03 / 0.01 s | 0.08 / 0.03 s | 0.03 / 0.03 s |
| Build after a one-function edit (CRUD / hello) | 2.9 / 1.2 s | 0.24 / 0.09 s | 0.50 / 0.28 s |
| Peak build memory, cold CRUD build | 410 MB | 311 MB | 992 MB |
| Binary size, shipping flags (CRUD / hello) | 1.12 / 0.41 MB (cargo build --release) | 5.48 / 1.57 MB (-ldflags="-s -w") | 2.28 / 0.67 MB (xo build --release) |
| Binary size, plain build (CRUD / hello) | 1.12 / 0.41 MB (no debug info) | 8.03 / 2.36 MB (with DWARF) | 6.23 / 1.90 MB (with DWARF) |
| Hello startup, wall | 2.5 ms | 2.8 ms | 2.6 ms |
Source: twins-build-2026-10-24.md, lines 20 to 25, 39 to 48 and 58 to 65.
Rust’s binaries are the smallest, by a factor of 1.6 to 2 against Xo’s --release binaries (4.6 to 5.6 against the plain xo build, which keeps 4.1 MB of debug information in the CRUD server; Go’s plain build keeps its DWARF too), and its
no-op build is the fastest. Its cold build of the server is the
slowest, because it compiles axum, hyper, tokio and serde from source,
and a release build does not compile incrementally, so an edit rebuilds
the crate (a debug build, which is incremental, was not measured). Xo
builds use the most memory. Go’s build after a one-function edit (0.24 s) is faster than Xo’s (0.50 s). Xo’s hello started in 1.04 of Rust’s time
with 1.4 times its memory (2.6 MB against 1.8).
- Targets. Rust lists 8 Tier 1 host targets (“guaranteed to work”)
and many more at Tier 2 and 3
(platform support);
cross compiling adds a target with
rustupand usually needs a linker for it. Xo:xo build --targets=allbuilds linux/amd64, linux/arm64, darwin/amd64, darwin/arm64, windows/amd64, and wasip1/wasm from any host with its own backends and linkers, no C toolchain (core 11). - Tests and fuzzing. Rust has
cargo test, doc tests, and fuzzing through external crates (cargo-fuzz). Xo hastestblocks, property tests, deterministic concurrency tests (xo test --explore=N), and contract fuzzing ofrequires/ensurescontracts, on the Go backend only today (core 9). - Formatting and lints. Both have one formatter (
rustfmt,xo fmt); Rust has Clippy, Xo hasxo vet.
Writing it with AI agents
Rust is far better known: models have seen much more Rust than Xo, and
rust-analyzer and the compiler’s error messages are excellent. The
cost for an agent is the borrow checker and lifetimes, which can take
several rounds to satisfy. Xo is designed for the agent’s loop:
xo check --jsonreports every problem with a stableXOnnnncode and machine-applicable fixes; codes never change meaning (diagnostics).- Rust habits (
Vec<T>,vec![],implblocks,.unwrap(),&self,format!,ascasts, and others, 24 cases) get a diagnostic that names Rust and gives the Xo form, with a fix. xo def,xo refs,xo query,xo edit, andxo mcpanswer semantic questions and make edits through resolved symbols.
Benchmarks
Method. The same programs written twice, each in its own language’s
idiom (Rust: plain loops, sort_unstable, tokio tasks and std threads,
serde_json, axum on tokio), and run under xo bench --ab with Rust as A
and Xo as B, 10 interleaved rounds; a verdict is “better” or “worse”
only when the 95% interval excludes 1.0, and the file asks that ratios
within 10% be read as ties. Every program prints a checksum and the run
stops if Rust’s and Xo’s differ. Machine: Apple M4 Pro (12 cores),
macOS 27, darwin/arm64, with other jobs running (each row waited for a
load under 5). Versions: rustc 1.99.1 with opt-level 3, one codegen
unit, thin LTO and panic = "abort"; axum 0.8.9, tokio 1.54.1,
serde_json 1.0.154; Xo from main between 0.26.0 and 0.27.0, native
backend. Date: 2026-10-23. Ratios are Xo over Rust; under 1.0 means
Xo took less
(rust-vs-xo-2026-10-23.md, lines 1 to 12 and 146 to 151).
Compute
| workload | wall time, Xo / Rust | verdict | source |
|---|---|---|---|
| strings | 0.44 | better | line 47 |
| sort | 0.93 | better | line 32 |
| sieve | 0.99 | within noise | line 22 |
| interpreter | 1.22 | worse | line 37 |
| fib | 1.31 | worse | line 17 |
| collatz | 1.42 | worse | line 42 |
| n-body (floating point) | 2.23 | worse | line 27 |
Peak memory: Xo used about 1.4 times Rust’s on fib, nbody, interp and collatz (2.6 MB against 1.8 MB), and 0.28 to 0.47 of Rust’s on sieve, sort and strings (lines 20 to 50).
Concurrency (Rust on tokio’s default multi thread runtime and std threads; the three async rows are also given on tokio’s current thread runtime, the Rust idiom for many small tasks that never need a second thread)
| workload | wall time, Xo / Rust | verdict | source |
|---|---|---|---|
| ping-pong (multi thread runtime) | 0.033 | better | line 65 |
| fanout (multi thread runtime) | 0.87 | better | line 60 |
| parallel map (std threads) | 1.27 | worse | line 80 |
| spawn (multi thread runtime) | 1.39 | worse | line 55 |
| ping-pong (current thread runtime) | 1.49 | worse | line 95 |
| mutex | 1.98 | worse | line 70 |
| rwmutex | 2.48 | worse | line 75 |
| fanout (current thread runtime) | 2.62 | worse | line 90 |
| spawn (current thread runtime) | 3.31 | worse | line 85 |
The large ping-pong win is tokio’s multi thread runtime waking another worker on every hop; on the current thread runtime, which is how a Rust programmer would write that program, Rust is faster. Xo used more CPU than Rust on spawn (2.3 times) and parallel map (1.33 times), less on multi thread ping-pong, and 1.1 to 3.4 times Rust’s peak memory on every concurrency row (lines 55 to 98).
JSON (serde_json against Xo’s std/json; 1,000 users, 100 rounds)
| workload | wall time, Xo / Rust | verdict | source |
|---|---|---|---|
| decode | 1.54 | worse | line 103 |
| encode | 1.53 | worse | line 113 |
For scale, the same file’s Go column: Xo took 0.36 of Go’s encoding/json time to decode and 0.71 to encode (lines 108 and 118).
HTTP: the users CRUD API (axum on tokio with serde_json, against Xo’s std/http with request arenas), 32 workers on loopback, 10 s measured after a 2 s warmup, the median of 3 runs (lines 137 to 143):
| Rust (axum) | Xo | Xo / Rust | |
|---|---|---|---|
| requests/s | 117,564 | 135,452 | 1.15 |
| p50 latency | 213 µs | 205 µs | 0.96 |
| p99 latency | 1,193 µs | 823 µs | 0.69 |
| p99.9 latency | 5,578 µs | 1,786 µs | 0.32 |
| server CPU per request | 17.61 µs | 21.14 µs | 1.20 |
| peak RSS | 5.0 MB | 18.7 MB | 3.7 |
Xo served more requests in each of the three runs (lines 128 to 135), but axum used less CPU per request and about a quarter of Xo’s memory. The runs’ spread is wide (the third reached about half the rate of the first two, for every server), so read the throughput ratio as “about the same to somewhat faster”, not as 15%.
These are a young compiler’s numbers on one machine; the slower rows
are the work queue (floating point, locks, task spawn, JSON). The
comparison is rerun from bench/perf/rust/run.sh and this page’s tables
are refreshed from its results file.