Rust
This article is about the Rust programming language. There are 3 methods to use the rust compiler and toolchain in Nix/NixOS:
- via nixpkgs,
- via rustup,
- or with unofficial overlays on nixpkgs.
Installing via nixpkgs is the nix-iest way to use rust, but there are valid reasons to use any approach.
Installing via nixpkgs
The cargo
and rustc
derivations provide the rust toolchain in nixpkgs. A pro of using nixpkgs is that it's dead-simple and you get pinned versions, deterministic builds in nix-shell, etc. However, nixpkgs only maintains a single version of the rust stable toolchain, so if you require a nightly toolchain or require switching between multiple toolchains then this approach may not be for you.
Here's an example shell.nix
:
let
# Pinned nixpkgs, deterministic. Last updated: 2/12/21.
pkgs = import (fetchTarball("https://github.com/NixOS/nixpkgs/archive/a58a0b5098f0c2a389ee70eb69422a052982d990.tar.gz")) {};
# Rolling updates, not deterministic.
# pkgs = import (fetchTarball("channel:nixpkgs-unstable")) {};
in pkgs.mkShell {
buildInputs = [ pkgs.cargo pkgs.rustc ];
}
VSCode integration
The rust-lang.rust and matklad.rust-analyzer VSCode extensions offer rust support. However, you'll need a few more ingredients to get everything working:
let
# Pinned nixpkgs, deterministic. Last updated: 2/12/21.
pkgs = import (fetchTarball("https://github.com/NixOS/nixpkgs/archive/a58a0b5098f0c2a389ee70eb69422a052982d990.tar.gz")) {};
# Rolling updates, not deterministic.
# pkgs = import (fetchTarball("channel:nixpkgs-unstable")) {};
in pkgs.mkShell {
buildInputs = [
pkgs.cargo
pkgs.rustc
pkgs.rustfmt
];
# Certain Rust tools won't work without this
# This can also be fixed by using oxalica/rust-overlay and specifying the rust-src extension
# See https://discourse.nixos.org/t/rust-src-not-found-and-other-misadventures-of-developing-rust-on-nixos/11570/3?u=samuela. for more details.
RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}";
}
You can use the arrterian.nix-env-selector extension to enable your nix-shell inside VSCode and have these settings picked up by other extensions.
Installation via rustup
The rustup tool is maintained by the rust community and offers and interface to install and switch between rust toolchains. In this scenario, rustup handles the "package management" of rust toolchains and places them in $PATH
. Nixpkgs offers rustup via the rustup
derivation. More info on using rustup can be found on their official website: https://rustup.rs/.
If you want to have the most "normal" rust experience I recommend using rustup with the following example shell.nix:
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell rec {
buildInputs = with pkgs; [
llvmPackages_latest.llvm
llvmPackages_latest.bintools
zlib.out
rustup
xorriso
grub2
qemu
llvmPackages_latest.lld
python3
];
RUSTC_VERSION = pkgs.lib.readFile ./rust-toolchain;
# https://github.com/rust-lang/rust-bindgen#environment-variables
LIBCLANG_PATH= pkgs.lib.makeLibraryPath [ pkgs.llvmPackages_latest.libclang.lib ];
HISTFILE=toString ./.history;
shellHook = ''
export PATH=$PATH:~/.cargo/bin
export PATH=$PATH:~/.rustup/toolchains/$RUSTC_VERSION-x86_64-unknown-linux-gnu/bin/
'';
# Add libvmi precompiled library to rustc search path
RUSTFLAGS = (builtins.map (a: ''-L ${a}/lib'') [
pkgs.libvmi
]);
# Add libvmi, glibc, clang, glib headers to bindgen search path
BINDGEN_EXTRA_CLANG_ARGS =
# Includes with normal include path
(builtins.map (a: ''-I"${a}/include"'') [
pkgs.libvmi
pkgs.glibc.dev
])
# Includes with special directory paths
++ [
''-I"${pkgs.llvmPackages_latest.libclang.lib}/lib/clang/${pkgs.llvmPackages_latest.libclang.version}/include"''
''-I"${pkgs.glib.dev}/include/glib-2.0"''
''-I${pkgs.glib.out}/lib/glib-2.0/include/''
];
}
It's important to have a file named `rust-toolchain` lying in the same directory as the shell.nix. It's purpose is to pin the version of the used rust compiler.
$ cat rust-toolchain
nightly-2021-09-19
The imporant part is that this also works with complex setups using `bindgen` and precompiled c libraries. To add a new c library in the search path of bindgen and rustc edit the variables `BINDGEN_EXTRA_CLANG_ARGS` and `RUSTFLAGS`
Unofficial overlays
- https://github.com/oxalica/rust-overlay (Flake support, Nightly & Stable)
- https://github.com/nix-community/fenix (Flake support, Nightly & Stable)
- https://github.com/mozilla/nixpkgs-mozilla (Nightly & Stable)
Rust Nightlies
- Use one of the overlays above,
- Or, use rustup
Developing Rust projects using Nix
Neovim Completion
Racer completion can be configured using the following snippet:
(neovim.override {
configure = {
customRC = ''
if filereadable($HOME . "/.vimrc")
source ~/.vimrc
endif
let $RUST_SRC_PATH = '${stdenv.mkDerivation {
inherit (rustc) src;
inherit (rustc.src) name;
phases = ["unpackPhase" "installPhase"];
installPhase = ''cp -r library $out'';
}}'
'';
packages.nixbundle.start = with vimPlugins; [
nvim-completion-manager
nvim-cm-racer
];
};
})
Packaging Rust projects with nix
At the time of writing, there are now no less than 6 different solutions for building Rust code with Nix. In the following table they are compared:
Name | Cargo.lock solution | Derivations | Build logic | Supports cross | Notes |
buildRustPackage
|
Checksum | 1 | cargo | Yes | Built into nixpkgs |
carnix
|
Codegen | Many | buildRustCrate
|
No? | Unmaintained, AFAICT; repository is gone |
crate2nix
|
Codegen (with optional IFD) | Many | buildRustCrate
|
No | Spiritual successor to carnix; nobody currently spending much time maintaining it |
naersk
|
Import | 2 | cargo | No? | Seems to only support building on x86 |
cargo2nix
|
Codegen | Many | Custom | Yes | Seems to only support prebuilt rustc from the nixpkgs-mozilla overlay, not rustc provided by Nixpkgs
|
import-cargo
|
Import | 1 | cargo | Unclear | More of a proof of concept than a full working solution |
Explanation for the columns
- Cargo.lock solution: How does this solution handle reproducibly determining what crates need to be downloaded from the Cargo.lock file? “Checksum” means it requires you to specify the checksum of all the downloaded dependencies. “Import” means it dynamically imports and parses Cargo.lock from a Nix expression, which means Cargo.lock needs to be present in the same repository as the nix expressions (or IFD must be used). “Codegen” means it generates a .nix file from the Cargo.lock, which is then committed to source control.
- Derivations: How many derivations does this solution use to compile rust code? “1” means the project and all its dependencies are compiled in one derivation. “2” means all dependencies are moved into a separate derivation, so the project can be updated independently, but any change to the set of dependencies rebuilds everything. “Many” means each dependency is built in its own derivation, so changes to dependencies only do the minimal amount of rebuilding necessary (and, ideally, different projects can share dependences, although I haven’t checked if this works in practice).
- Build logic: How does this solution orchestrate building of crates? “Cargo” means it relies on Cargo;
buildRustCrate
means it uses Nixpkgs’buildRustCrate
; “custom” means it uses its own custom logic (in Nix) - Supports cross: Does the solution allow for cross-compilation of crates?
Shell.nix example
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
nativeBuildInputs = with pkgs; [ rustc cargo gcc ];
buildInputs = with pkgs; [ rustfmt clippy ];
RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}";
}
This will have the stable rust compiler + the official formatter and linter inside the ephemeral shell. It'll also set the RUST_SRC_PATH environment variable to point to the right location, which tools, such as rust-analyzer, require to be set.
FAQ
Building the openssl-sys crate
You'll need to have the openssl
and pkg-config
derivatives in order to build openssl-sys
crate. For example, you can start a shell providing these packages:
$ nix-shell -p pkg-config openssl
In some cases (eg here) you may also need
PKG_CONFIG_PATH = "${pkgs.openssl.dev}/lib/pkgconfig";