Skip to content

Closing the Nix Gap: From Environments to Packaged Applications for Rust

This tweet shows a common problem in Nix: "Should I use crate2nix, cargo2nix, or naersk for packaging my Rust application?"

devenv solved this for development environments differently: instead of making developers package everything with Nix, we provide tools through a simple languages.rust.enable. You get cargo, rustc, and rust-analyzer in your shell without understanding Nix packaging.

But when you're ready to deploy, you face the same problem: which lang2nix tool should you use? Developers don't want to compare crate2nix vs cargo2nix vs naersk vs crane—they want a tested solution that works.

devenv now provides languages.rust.import, which packages Rust applications using crate2nix. We evaluated the available tools and chose crate2nix, so you don't have to.

We've done this before. In PR #1500, we replaced fenix with rust-overlay for Rust toolchains because rust-overlay was better maintained. Users didn't need to change anything—devenv handled the transition while keeping the same languages.rust.enable = true interface.

One Interface for All Languages

The typical workflow:

  1. Development: Enable the language (languages.rust.enable = true) to get tools like cargo, rustc, and rust-analyzer.
  2. Packaging: When ready to deploy, use languages.rust.import to package with Nix.

The same pattern works for all languages:

{ config, ... }: {
  # https://devenv.sh/languages
  languages = {
    rust.enable = true;
    python.enable = true;
    go.enable = true;
  };

  # https://devenv.sh/outputs
  outputs = {
    rust-app = config.languages.rust.import ./rust-app {};
    python-app = config.languages.python.import ./python-app {};
    go-app = config.languages.go.import ./go-app {};
  };
}

Starting with Rust

languages.rust.import automatically generates Nix expressions from Cargo.toml and Cargo.lock.

Add the crate2nix input:

$ devenv inputs add crate2nix github:nix-community/crate2nix --follows nixpkgs

Import your Rust application:

{ config, ... }:
let
  # ./app is the directory containing your Rust project's Cargo.toml
  myapp = config.languages.rust.import ./app {};
in
{
  # Provide developer environment
  languages.rust.enable = true;

  # Expose our application inside the environment
  packages = [ myapp ];

  # https://devenv.sh/outputs
  outputs = {
    inherit myapp;
  };
}

Build your application:

$ devenv build outputs.myapp

Other Languages

This API extends to other languages, each using the best packaging tool:

We've also started using uv2nix to provide a similar interface for Python in PR #2115.

That's it

For feedback, join our Discord community.

Domen