[[!meta date="2026-10-11 12:00"]]
[[!tag nix libnix cargo rust cargo+libnix]]
[[!series libnix]]
[[!summary cargo libnix: nix-daemon protocol, IFD support and crates in the nix store]]

[[!img posts/libnix/libnix-rust-logo.png class="noFancy" style="float: right" width="200px"]]

# motivation

this is a short status update on `cargo (libnix)` covering what changed since the `libnix-1.87.0+v1` release. two releases happened since then: `libnix-1.87.0+v2` and `libnix-1.87.0+v3`.

the code can be found here:

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

# improvements

## talking to the nix-daemon directly

`cargo build` with the nix backend no longer spawns `nix build` as a subprocess. instead it evaluates the build system with `nix-instantiate` and then talks the nix worker protocol to the nix-daemon directly, sending a single build request for the target and all its dependencies.

the daemon reports a structured result per crate, so cargo knows exactly which crate failed, which ones were skipped because of it and which ones came from a binary cache. `-j` and `--keep-going` are passed on to the daemon.

## logone is now part of cargo

the external [logone](https://github.com/nixcloud/logone) crate was replaced by an in-tree renderer that reads the daemon's log stream:

```
   Evaluated target in 1.4s
 Downloading serde 1.0.218
   Compiling proc-macro2 (build.rs run)
   Compiling serde
[ 51 Done | 613 Expected | 8 Running | 0 Failed ] fiat-crypto, jiff (×2), libc (build.rs build)
```

* `Compiling`, `Building` and `Downloading` lines similar to vanilla cargo
* rustc diagnostics are shown as cargo would show them
* failed crates come with their build log, dependents are listed as skipped
* a status line with correct counts (nix's own counter overcounts)
* the time spent evaluating the nix build system is printed

## parallel builds again

dependency flags of build scripts used to be read with `builtins.readFile` at evaluation time, which forced crates to be built one after another. the flags are now collected in the `buildPhase` into a rustc response file, so nix schedules independent crates in parallel again (#11).

## support for IFD

`cargo build write-nix-buildsystem` now only needs `--out-dir`. without `--url`/`--hash` the generated build system points to the local project directory, so it can be generated inside a derivation and imported (#22):

```bash
CARGO_BACKEND=nix cargo build --offline write-nix-buildsystem --out-dir $out
```

`cargo_build_caller.nix` takes `project_root` and `system` as arguments and evaluates in pure (flake) mode, so a flake can build any rust project from its own source this way. the `flake.nix` of the cargo fork uses this to build `cargo (libnix)` from the current checkout: `nix build .#cargo-libnix-master-via-ifd`.

the export with `--url`/`--hash` for releases still works as before.

## crates live in the nix store

in nix mode cargo **never downloads a crate itself anymore** (#24). each crates.io crate is fetched into the nix store by the nix-daemon, under the same store path the generated build system's `fetchurl` uses. so crate sources are fetched once and shared between cargo and the nix build, and they can come from cache.nixos.org.

`~/.cargo/registry/cache` stays empty and `-v` shows the store path next to each `Downloading` line.

without the nix-daemon socket cargo stops with an error, there is no fallback to cargo's own downloads.

## no more crates.io index for offline builds

with `--offline` and a `Cargo.lock` the resolver no longer needs the crates.io index. crates.io is served from the crate tarballs in the nix store instead (#22).

this means the IFD from above needs **no vendoring and no network**, the crate tarballs only have to be inputs of the derivation. they can be computed from `Cargo.lock` in plain nix:

```nix
lock = builtins.fromTOML (builtins.readFile ./Cargo.lock);
crateTarballs = map (p: pkgs.fetchurl {
    name = "crate-${p.name}-${p.version}.tar.gz";
    url = "https://crates.io/api/v1/crates/${p.name}/${p.version}/download";
    sha256 = p.checksum;
  })
  (builtins.filter (p: (p.source or "") == "registry+https://github.com/rust-lang/crates.io-index") lock.package);
```

## smaller things

* nixpkgs 26.05 and an updated fenix
* `cargo --version` reports `cargo 1.87.0 +libnix`
* `nix develop` works better: PATH, CA certificates and proxies are passed to nix
* the dev shell uses the `libnix-1.87.0+v2` release as its default cargo
* a clearer error when `target/nix` lacks the `.cargo-libnix-managed` marker (#17)

# experiments

the branch `20_using_nix_content_addressed` marks the `build.rs` run derivations as content addressed (`__contentAddressed = true`), so a build script that produces the same output doesn't trigger rebuilds downstream (#20). this needs the `ca-derivations` experimental feature, so it is not merged.

# ui example

<script async id="asciicast-1268068" src="https://asciinema.org/a/1268068.js"></script>

this shows the `cargo` build after a `nix-collect-garbage -d` so it also includes downloads of the toolchain, dependencies and the build of the main application.

# summary

`cargo (libnix)` is still slower than vanilla cargo, but the logging, the sharing of crates and builds via the nix store and now the IFD support make it a pleasant way to build rust projects with nix.


<div class="alert alert-info" role="info">
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.
</div>
