lastlog.de/blog
  • timeline
  • about
  • roadmap
  • websocket
  • drafts
  • special pages
  • login
prev. article
libnix

libnix rust v4 release

13 oct 2026

nixlibnixcargorustcargo+libnix

motivation

this is a short status update on cargo (libnix) covering what changed since the libnix-1.87.0+v3 release. with libnix-1.87.0+v4 the nix backend is no longer limited to cargo build: tests, cargo run and cargo install work as well, so a normal day of rust development can happen in nix mode.

the code can be found here:

https://github.com/nixcloud/cargo/blob/master-libnix/README.md

improvements

cargo test

cargo test works in nix mode: unit, integration and doc tests, with the same output as vanilla cargo.

the test harnesses and examples are built in nix like every other unit, from target/<profile>/nix-test with GC roots of their own, so cargo build and cargo test don’t delete each other’s store paths. the tests themselves run outside of nix, like with vanilla cargo:

  • OUT_DIR and rustc-env of the build scripts are read from the store
  • CARGO_BIN_EXE_<name> points at the binary in the store, and integration tests find the binaries of their package next to themselves (snapbox, assert_cmd)
  • doc tests use the rustdoc and linker the units were built with, so the host needs no toolchain
  • env!("CARGO_MANIFEST_DIR") points at the sources in the store, which are read-only: fixtures can be read, not written

cargo run

cargo run runs binaries and examples with the arguments, exit code, working directory, OUT_DIR and rustc-env of vanilla cargo. target/debug/<bin> is a symlink into the store, so std::env::current_exe() is the store path.

cargo install and cargo uninstall

installed binaries stay in the nix store, since they link against store paths (glibc, the libraries from Cargo.dependencies.nix). each one is protected by an indirect GC root and ~/.cargo/bin links to it through that root:

~/.cargo/
├── .crates.toml / .crates2.json          unchanged, cargo install --list works
├── nix/install/
│   └── sd-1.0.0-<hash>/
│       └── sd  ->  /nix/store/…-sd-1_0_0-bin-…
└── bin/
    └── sd  ->  ../nix/install/sd-1.0.0-<hash>/sd/sd-<hash>

crates.io packages are fetched into the store like any other dependency. upgrading to another version replaces the root, and cargo uninstall removes the links and deletes the binary from the store, unless something else still uses it:

    Removing /home/joachim/.cargo/bin/sd
     Removed 3 store path(s), 2.4MiB

crate sources and crate builds of an install are left in the store for nix-collect-garbage, like ~/.cargo/registry they are shared with other builds.

GC roots for everything a build uses

before v4 only the workspace’s own units were rooted, so a nix-collect-garbage removed every dependency build and every crate tarball and the next build started over (#4).

now the generated build system lists every unit output, crate tarball, the toolchain and the external dependencies, and that list is rooted in target/<profile>/nix/gc. after a successful build, cargo deletes what the previous build used and the new one doesn’t, through the nix-daemon, unless another project or a running process still uses it:

    Finished 16 built, 0 fetched, 0 fresh, 0 failed, 0 skipped in 80.6s
     Removed 15 unused store path(s), 431.9MiB

each selection of root units (cargo build, cargo run --bin a, cargo test -p x, other features) has a slot of its own, so building a subset doesn’t delete what the full build uses. the 8 most recently built selections are kept.

cargo clean works in nix mode as well: it removes the target directory (or a profile) and deletes the store paths its builds rooted, --dry-run counts them.

build failures at the end

failures used to be printed as they happened and got interleaved with the output of other builds. now they are collected and printed after all builds are done, each under a header naming the crate and its derivation:

──── error 1/1: sd (bin) (sd-1_0_0-bin-a45bb84d6cc05f0a) ───────────────────────
       error failed to build `sd (bin)` in phase `buildPhase`: PermanentFailure

git dependencies

two fixes, both found with RustPython:

  • git dependencies outside ~/.cargo/git/checkouts (vendored sources, source replacement) no longer crash the generator
  • git crates are built in a writable copy of their repository instead of the read-only store, which also fixes crates in a subdirectory of their repository

regression tests

test.nix builds the projects of #18 at pinned revisions with the cargo (libnix) of the checkout: it generates each build system in nix (IFD), builds it with rustc/cargo 1.87 and runs every binary with --version. tests-libnix/run.sh builds them one by one with --keep-going and writes a RESULTS.md.

nix-build test.nix -A ripgrep.smoke
tests-libnix/run.sh

23 of 31 projects build and run, among them nushell, rust-analyzer, RustPython, ripgrep, bat, atuin, vaultwarden, tokio and axum. the other 8 (codex, leptos, mdBook, sniffnet, starship, typst, union, wasmtime) need a rustc newer than 1.87 at those revisions, that’s a toolchain limitation and not a libnix one.

smaller things

  • flake.nix: cargo-libnix-git-head-via-ifd (the working tree) and cargo-libnix-git-revision-via-ifd (a pinned revision) build cargo (libnix) from source via IFD
  • the dev shell uses the libnix-1.87.0+v4 release as its default cargo
  • the README got sections on supported subcommands and features, how it works, requirements, a copy-pasteable shell.nix and how to export the build system for your own project

known issue

the generated nix code still contains one absolute path: the path of the .nix file of each build script run ends up in an error message. so a different output directory rebuilds the build script runs and everything depending on them. you notice it with cargo install, which builds in a new temporary directory every time, and with the IFD builds, which rebuild after every change to cargo. a fix is planned.

outlook

the next big topic is faster iteration (#3): computing a minimal file list per unit from rustc’s dep-info, so nix only rebuilds a root crate when one of its source files changed, and not when someone edits the README.md.

summary

with v4 cargo (libnix) covers build, test, run, install and clean, keeps its store paths alive as long as they are needed and cleans up after itself. it is still slower than vanilla cargo, but it is now usable for a normal day of rust development.

if you like this work, your support is needed!

  • consider a star at https://github.com/nixcloud/cargo/stargazers to show your support.
  • comments are welcome in https://discourse.nixos.org/t/libnix-feedback/75193
  • if you’re interested in supporting this development, please reach out to me, see https://lastlog.de/cv for details.
article source