Skip to main content

Nix shell with rustup

·3 mins

In this quick blogpost I want to show how you can setup a rustup toolchain with nix. Instead of using the rustup cli app, we will be fetching the toolchains directly with nix.

Why do we want to do this, instead of just adding rustup to our environment.systemPackages or to a shell? rustup is a program that will fetch the toolchain from the internet, and as you may already know, fetching binaries from the internet is bound to fail at some point in NixOS.

Another nicety that we get, is that we can use nix flake’s locking system, so that everybody that develops the projects (and CI too!) will get the same toolchain version.

rust-toolchain #

The rust-toolchain or toolchain.toml is a file used by rustup that declares what channel to use, which components, targets, etc. This file lives in the root of the repo. Oxalica’s rust-overlay then would read this file, and produce the required nix derivation according to the requirements.

So instead of running rustup, we just point the overlay to rustup’s config file. Nice and easy.

An example of this file:

# toolchain.toml
[toolchain]
channel = "nightly"
components = [ "rustfmt", "rust-src" ]
profile = "minimal"

Classic nix #

This approach uses nix channels and fetchTarball, to get whatever overlay version is the latest. We don’t lock the overlay version, and it is as simple as it can get.

To use the shell, just run nix-shell

# shell.nix
let
  rust-overlay = builtins.fetchTarball "https://github.com/oxalica/rust-overlay/archive/master.tar.gz";
  pkgs = import <nixpkgs> {
    overlays = [(import rust-overlay)];
  };
  toolchain = pkgs.rust-bin.fromRustupToolchainFile ./toolchain.toml;
in
  pkgs.mkShell {
    packages = [
      toolchain
    ];
  }

Flakes #

By using a flake, we are able to lock the version of the rust overlay, so we always get the same version of the toolchain, for a given flake.lock. This is incredibly useful to avoid “it works on my machine”, by having every developer and CI use the same toolchain version, according to the specification of the rustup config.

To enter the shell, just run nix develop

# flake.nix
{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    rust-overlay.url = "github:oxalica/rust-overlay";
  };

  outputs = {
    self,
    nixpkgs,
    rust-overlay,
  }: let
    system = "x86_64-linux";
    pkgs = import nixpkgs {
      inherit system;
      overlays = [rust-overlay.overlays.default];
    };
    toolchain = pkgs.rust-bin.fromRustupToolchainFile ./toolchain.toml;
  in {
    devShells.${system}.default = pkgs.mkShell {
      packages = [
        toolchain
      ];
    };
  };
}

rust-analyzer #

For rust-analyzer to work properly, you will need to setup the environment variable RUST_SRC_PATH, which must point to a subdirectory of our toolchain. To do so, just modify your mkShell definition (flakes or not) such as:

pkgs.mkShell {
  packages = [
    toolchain

    # We want the unwrapped version, "rust-analyzer" (wrapped) comes with nixpkgs' toolchain
    pkgs.rust-analyzer-unwrapped
  ];

  RUST_SRC_PATH = "${toolchain}/lib/rustlib/src/rust/library";
}

Finally, make sure you include the rust-src component in your rustup toolchain definition:

# toolchain.toml
[toolchain]
components = [
  "rust-src"
  # ...
]
Fernando Ayats
Author
Fernando Ayats