Rust: Difference between revisions

From NixOS Wiki
imported>Luis-Hebendanz
No edit summary
Remove the recommendation for the rust-lang.rust extension for VSCode, since it is deprecated and was replaced by the already recommended rust-lang.rust-analyzer
 
(63 intermediate revisions by 37 users not shown)
Line 1: Line 1:
This article is about the [https://www.rust-lang.org Rust programming language]. There are 3 methods to use the rust compiler and toolchain in Nix/NixOS:  
This article is about the [https://www.rust-lang.org Rust programming language]. There are 3 methods to use the Rust compiler and toolchain in Nix/NixOS:


# via nixpkgs,  
# via nixpkgs,
# via rustup,  
# via rustup,
# or with unofficial overlays on nixpkgs.  
# 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 is the best way to use Rust, but there are valid reasons to use any approach.


== Installing via nixpkgs ==
== Installing via nixpkgs ==
The <code>cargo</code> and <code>rustc</code> 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.  
The <code>cargo</code> and <code>rustc</code> derivations provide the Rust toolchain in nixpkgs. An advantage 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 switch between multiple toolchains then this approach may not be for you.


Here's an example <code>shell.nix</code>:
Here's an example <code>shell.nix</code>:


<syntaxHighlight lang="nix">
<syntaxhighlight lang="nix">
let
let
   # Pinned nixpkgs, deterministic. Last updated: 2/12/21.
   # Pinned nixpkgs, deterministic. Last updated: 2/12/21.
Line 19: Line 19:
   # Rolling updates, not deterministic.
   # Rolling updates, not deterministic.
   # pkgs = import (fetchTarball("channel:nixpkgs-unstable")) {};
   # pkgs = import (fetchTarball("channel:nixpkgs-unstable")) {};
in pkgs.mkShell {
in
   buildInputs = [ pkgs.cargo pkgs.rustc ];
pkgs.callPackage (
}
  {
</syntaxHighlight>
    mkShell,
    cargo,
    rustc,
  }:
  mkShell {
    strictDeps = true;
    nativeBuildInputs = [
      cargo
      rustc
    ];
  }
) { }
</syntaxhighlight>
 
== Installating with bindgen support ==
By default crates using <code>bindgen</code> will not compile. To add bindegen support add the <code>rustPlatform.bindegenHook</code> to your <code>nativeBuildInputs</code>.
 
Here's an example <code>shell.nix</code>:
 
<syntaxhighlight lang="nix">
{
  pkgs ? import <nixpkgs> { },
}:
pkgs.callPackage (
  {
    mkShell,
    cargo,
    rustc,
    rustPlatform,
    pkg-config,
   }:
  mkShell {
    strictDeps = true;
    nativeBuildInputs = [
      cargo
      rustc
      rustPlatform.bindgenHook
      # optional: add pkg-config support
      pkg-config
    ];
    buildInputs = [
      # add desired native packages
      # ...
    ];
    # ...
  }
) { }
</syntaxhighlight>
 
This also works, when compiling rust crates:


=== VSCode integration ===
The [https://marketplace.visualstudio.com/items?itemName=rust-lang.rust rust-lang.rust] and [https://marketplace.visualstudio.com/items?itemName=matklad.rust-analyzer matklad.rust-analyzer] VSCode extensions offer rust support. However, you'll need a few more ingredients to get everything working:
<syntaxHighlight lang="nix">
<syntaxHighlight lang="nix">
let
{
   # Pinned nixpkgs, deterministic. Last updated: 2/12/21.
  rustPlatform,
   pkgs = import (fetchTarball("https://github.com/NixOS/nixpkgs/archive/a58a0b5098f0c2a389ee70eb69422a052982d990.tar.gz")) {};
   pkg-config,
 
   ...
   # Rolling updates, not deterministic.
}:
   # pkgs = import (fetchTarball("channel:nixpkgs-unstable")) {};
rustPlatform.buildRustPackage {
in pkgs.mkShell {
   # ...
   buildInputs = [  
   nativeBuildInputs = [
     pkgs.cargo
    rustPlatform.bindgenHook
     pkgs.rustc
    pkg-config
    pkgs.rustfmt
  ];
   buildInputs = [
     # add desired native packages
     # ...
   ];
   ];
  # 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}";
}
}
</syntaxHighlight>
</syntaxHighlight>
You can use the [https://marketplace.visualstudio.com/items?itemName=arrterian.nix-env-selector arrterian.nix-env-selector] extension to enable your nix-shell inside VSCode and have these settings picked up by other extensions.


== Installation via rustup ==
== 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 <code>$PATH</code>. Nixpkgs offers rustup via the <code>rustup</code> derivation. More info on using rustup can be found on their official website: https://rustup.rs/.
The rustup tool is maintained by the Rust community and offers an interface to install and switch between Rust toolchains. In this scenario, rustup handles the "package management" of Rust toolchains and places them in <code>$PATH</code>. Nixpkgs offers rustup via the <code>rustup</code> 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:
If you want the most "normal" Rust experience I recommend using rustup with the following example shell.nix:
<syntaxHighlight lang="nix">
<syntaxhighlight lang="nix">
{ pkgs ? import <nixpkgs> {} }:
{
   pkgs.mkShell rec {
  pkgs ? import <nixpkgs> { },
     buildInputs = with pkgs; [
}:
      llvmPackages_latest.llvm
let
      llvmPackages_latest.bintools
   overrides = (builtins.fromTOML (builtins.readFile ./rust-toolchain.toml));
      zlib.out
in
pkgs.callPackage (
  {
    stdenv,
    mkShell,
    rustup,
    rustPlatform,
  }:
  mkShell {
     strictDeps = true;
    nativeBuildInputs = [
       rustup
       rustup
       xorriso
       rustPlatform.bindgenHook
      grub2
      qemu
      llvmPackages_latest.lld
      python3
     ];
     ];
     RUSTC_VERSION = pkgs.lib.readFile ./rust-toolchain;
    # libraries here
    buildInputs =
      [
      ];
     RUSTC_VERSION = overrides.toolchain.channel;
     # https://github.com/rust-lang/rust-bindgen#environment-variables
     # https://github.com/rust-lang/rust-bindgen#environment-variables
    LIBCLANG_PATH= pkgs.lib.makeLibraryPath [ pkgs.llvmPackages_latest.libclang.lib ];
    HISTFILE=toString ./.history;
     shellHook = ''
     shellHook = ''
       export PATH=$PATH:~/.cargo/bin
       export PATH="''${CARGO_HOME:-~/.cargo}/bin":"$PATH"
       export PATH=$PATH:~/.rustup/toolchains/$RUSTC_VERSION-x86_64-unknown-linux-gnu/bin/
       export PATH="''${RUSTUP_HOME:-~/.rustup}/toolchains/$RUSTC_VERSION-${stdenv.hostPlatform.rust.rustcTarget}/bin":"$PATH"
      '';
    '';
    # Add libvmi precompiled library to rustc search path
  }
    RUSTFLAGS = (builtins.map (a: ''-L ${a}/lib'') [
) { }
      pkgs.libvmi
 
    ]);
</syntaxhighlight>
    # Add libvmi, glibc, clang, glib headers to bindgen search path
 
    BINDGEN_EXTRA_CLANG_ARGS =
It's important to have a file named <code>rust-toolchain.toml</code> lying in the same directory as the shell.nix.
    # Includes with normal include path
Rust already has a standardized way of pinning a toolchain version for a workspace or a project.
    (builtins.map (a: ''-I"${a}/include"'') [
See [https://rust-lang.github.io/rustup/overrides.html#the-toolchain-file the Rustup book] for its syntax.
      pkgs.libvmi
 
      pkgs.glibc.dev
A minimal example of the <code>rust-toolchain.toml</code>:
    ])
<syntaxhighlight lang="toml">
    # Includes with special directory paths
[toolchain]
    ++ [
channel = "stable" # This can also be "nightly" if you want a nightly rust
      ''-I"${pkgs.llvmPackages_latest.libclang.lib}/lib/clang/${pkgs.llvmPackages_latest.libclang.version}/include"''
                  # or nightly-20XX-XX-XX for a specific nightly.
      ''-I"${pkgs.glib.dev}/include/glib-2.0"''
</syntaxhighlight>
      ''-I${pkgs.glib.out}/lib/glib-2.0/include/''
    ];


  }
== Cross-compiling ==
</syntaxHighlight>


It's important to have a file named `rust-toolchain` lying in the same directory as the shell.nix.
=== To Windows via rustup ===
It's purpose is to pin the version of the used rust compiler.
<syntaxHighlight lang="bash">
$ cat rust-toolchain
nightly-2021-09-19
</syntaxHighlight>


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`
* [https://github.com/jraygauthier/jrg-rust-cross-experiment/tree/master/simple-static-rustup-target-windows simple-static-rustup-target-windows]
** [https://github.com/jraygauthier/jrg-rust-cross-experiment/blob/master/simple-static-rustup-target-windows/shell.nix shell.nix]


== Unofficial overlays ==
== Unofficial overlays ==
Line 109: Line 153:
# https://github.com/oxalica/rust-overlay (Flake support, Nightly & Stable)
# https://github.com/oxalica/rust-overlay (Flake support, Nightly & Stable)
# https://github.com/nix-community/fenix (Flake support, Nightly & Stable)
# https://github.com/nix-community/fenix (Flake support, Nightly & Stable)
# https://github.com/mozilla/nixpkgs-mozilla (Nightly & Stable)
# https://github.com/mozilla/nixpkgs-mozilla (Flake support, Nightly & Stable)


== Rust Nightlies ==
== devenv.sh support ==


# Use one of the overlays above,
# https://github.com/cachix/devenv/blob/main/examples/rust/devenv.nix and <code>devenv shell</code>
# Or, use rustup


== Developing Rust projects using Nix ==
== Developing Rust projects using Nix ==


# https://notes.srid.ca/rust-nix
The [https://nixos.org/manual/nixpkgs/stable/#rust Nixpkgs manual] uses <code>buildRustPackage</code>.


== Neovim Completion ==
[https://srid.ca/rust-nix This] blog post shows how to do it using <code>dream2nix</code>. A template repo is available here: https://github.com/srid/rust-nix-template


Racer completion can be configured using the following snippet:
== Using overrideAttrs with Rust Packages ==
 
<syntaxhighlight lang="nix">
<syntaxHighlight lang="nix">
nil = pkgs.nil.overrideAttrs (
(neovim.override {
  finalAttrs: previousAttrs: {
  configure = {
    version = "unstable-2024-09-19";
     customRC = ''
                                                                         
       if filereadable($HOME . "/.vimrc")
     src = pkgs.fetchFromGitHub {
        source ~/.vimrc
       owner = "oxalica";
       endif
      repo = "nil";
       let $RUST_SRC_PATH = '${stdenv.mkDerivation {
       rev = "c8e8ce72442a164d89d3fdeaae0bcc405f8c015a";
        inherit (rustc) src;
       hash = "sha256-mIuOP4I51eFLquRaxMKx67pHmhatZrcVPjfHL98v/M8=";
        inherit (rustc.src) name;
    };
        phases = ["unpackPhase" "installPhase"];
                                                                         
        installPhase = ''cp -r library $out'';
    # Requires IFD
      }}'
    cargoDeps = pkgs.rustPlatform.importCargoLock {
     '';
      lockFile = finalAttrs.src + "/Cargo.lock";
     packages.nixbundle.start = with vimPlugins; [
      allowBuiltinFetchGit = true;
      nvim-completion-manager
    };
      nvim-cm-racer
    cargoHash = null;
     ];
                                                                         
   };
     # Doesn't require IFD
})
     #cargoDeps = previousAttrs.cargoDeps.overrideAttrs {
</syntaxHighlight>
    #  name = "nil-vendor.tar.gz";
    #  inherit (finalAttrs) src;
    #  #outputHash = pkgs.lib.fakeHash;
    #  outputHash = "sha256-RWgknkeGNfP2wH1X6nc+b10Qg1QX3UeewDdeWG0RIE8=";
     #};
   }
);
</syntaxhighlight>


== Packaging Rust projects with nix ==
== 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:
At the time of writing, there are now no less than 8 different solutions for building Rust code with Nix. In the following table they are compared:


{|
{|
Line 164: Line 214:
| Yes
| Yes
| Built into nixpkgs
| Built into nixpkgs
|-
| <code>carnix</code>
| Codegen
| Many
| <code>buildRustCrate</code>
| No?
| Unmaintained, AFAICT; [https://nest.pijul.com/pmeunier/carnix:master repository] is gone
|-
|-
| [https://github.com/kolloch/crate2nix <code>crate2nix</code>]
| [https://github.com/kolloch/crate2nix <code>crate2nix</code>]
Line 176: Line 219:
| Many
| Many
| <code>buildRustCrate</code>
| <code>buildRustCrate</code>
| No
| [https://github.com/kolloch/crate2nix/commit/8bfeb42bda097e0bdf5452691a5e157aad3cc11f experimental]
| Spiritual successor to carnix; [https://github.com/kolloch/crate2nix/pull/152#issuecomment-715603056 nobody currently spending much time maintaining it]
| Spiritual successor to [https://github.com/nix-community/carnix <code>carnix</code>]
|-
|-
| [https://github.com/nmattia/naersk/ <code>naersk</code>]
| [https://github.com/nmattia/naersk/ <code>naersk</code>]
Line 183: Line 226:
| 2
| 2
| cargo
| cargo
| [https://github.com/nmattia/naersk/issues/79 No?]
| Yes
| [https://github.com/nmattia/naersk/blob/22b96210b2433228d42bce460f3befbdcfde7520/rust/rustc.nix#L22-L29 Seems to only support building on x86]
| [https://github.com/nmattia/naersk/blob/22b96210b2433228d42bce460f3befbdcfde7520/rust/rustc.nix#L22-L29 Seems to only support building on x86]
|-
|-
Line 189: Line 232:
| Codegen
| Codegen
| Many
| Many
| Custom
| cargo + custom
| Yes
| Yes
| Seems to only support prebuilt rustc from the <code>nixpkgs-mozilla</code> overlay, not rustc provided by Nixpkgs
| Defaults to the oxalica Rust overlay but this can be overridden with <code>rustToolchain</code>
|-
|-
| [https://github.com/edolstra/import-cargo <code>import-cargo</code>]
| [https://github.com/edolstra/import-cargo <code>import-cargo</code>]
Line 199: Line 242:
| Unclear
| Unclear
| More of a proof of concept than a full working solution
| More of a proof of concept than a full working solution
|-
| [https://github.com/ipetkov/crane <code>crane</code>]
| Import
| 2
| cargo
| [https://github.com/ipetkov/crane/tree/master/examples Yes]
| Inspired by naersk, with [https://discourse.nixos.org/t/introducing-crane-composable-and-cacheable-builds-with-cargo/17275/4 better support for composing Cargo invocations as completely separate derivations]
|-
| [https://dream2nix.dev/reference/rust-crane <code>dream2nix</code>]
| Codegen
| 1 or 2
| cargo (via <code>buildRustPackage</code> or <code>crane</code>)
| Yes
| A framework for unifying 2nix converters across languages (Experimental)
|}
|}


Line 204: Line 261:


* '''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.
* '''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).
* '''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 dependencies, 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; <code>buildRustCrate</code> means it uses Nixpkgs’ <code>buildRustCrate</code>; “custom” means it uses its own custom logic (in Nix)
* '''Build logic:''' How does this solution orchestrate building of crates? “Cargo” means it relies on Cargo; <code>buildRustCrate</code> means it uses Nixpkgs’ <code>buildRustCrate</code>; “custom” means it uses its own custom logic.  <code>buildRustPackage</code> means it uses Nixpkgs' <code>buildRustPackage</code>, which in turn uses Cargo.
* '''Supports cross:''' Does the solution allow for cross-compilation of crates?
* '''Supports cross:''' Does the solution allow for cross-compilation of crates?


== Shell.nix example ==
== Shell.nix example ==
<syntaxHighlight lang="nix">
<syntaxhighlight lang="nix">
{ pkgs ? import <nixpkgs> {} }:
{
pkgs.mkShell {
  pkgs ? import <nixpkgs> { },
  nativeBuildInputs = with pkgs; [ rustc cargo gcc ];
}:
   buildInputs = with pkgs; [ rustfmt clippy ];
 
pkgs.callPackage (
  {
    mkShell,
    rustc,
    cargo,
    rustPlatform,
    rustfmt,
    clippy,
    rust-analyzer,
  }:
  mkShell {
    strictDeps = true;
    nativeBuildInputs = [
      rustc
      cargo
      rustfmt
      clippy
      rust-analyzer
    ];
 
    # Certain Rust tools won't work without this
    # rust-analyzer from nixpkgs does not need 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 = "${rustPlatform.rustLibSrc}";
  }
) { }
</syntaxhighlight>
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.
 
=== Custom Rust version or targets ===
 
<syntaxhighlight lang="nix">
let
  rustVersion = "latest";
  #rustVersion = "1.62.0";
   rust_overlay = import (
    builtins.fetchTarball "https://github.com/oxalica/rust-overlay/archive/master.tar.gz"
  );
  pkgs = import <nixpkgs> {
    overlays = [
      rust_overlay
      (_: prev: {
        my-rust = prev.rust-bin.stable.${rustVersion}.default.override {
          extensions = [
            "rust-src" # for rust-analyzer
            "rust-analyzer"
          ];
          # Or import nixpkgs with `crossSystem`
          #targets = [ "arm-unknown-linux-gnueabihf" ];
        };
      })
    ];
 
  };
in
pkgs.callPackage (
  {
    mkShell,
    hello,
    my-rust,
    pkg-config,
    openssl,
  }:
  mkShell {
    strictDeps = true;
    # host/target agnostic programs
    depsBuildBuild = [
      hello
    ];
    # compilers & linkers & dependecy finding programs
    nativeBuildInputs = [
      my-rust
      pkg-config
    ];
    # libraries
    buildInputs = [
      openssl
    ];
    RUST_BACKTRACE = 1;
  }
) { }
 
</syntaxhighlight>
 
=== VSCode integration ===
The [https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer rust-lang.rust-analyzer] VSCode extension offers Rust support.


  RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}";
You can use the [https://marketplace.visualstudio.com/items?itemName=arrterian.nix-env-selector arrterian.nix-env-selector] extension to enable your nix-shell inside VSCode and have these settings picked up by other extensions.
}
</syntaxHighlight>
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 ==
== FAQ ==
=== Building the openssl-sys crate ===
 
You'll need to have the <code>openssl</code> and <code>pkg-config</code> derivatives in order to build <code>openssl-sys</code> crate. For example, you can start a shell providing these packages:
=== Rust from a rustup toolchain file ===
<syntaxHighlight lang=console>
<syntaxhighlight lang="nix">
let
  rust-overlay = builtins.fetchTarball "https://github.com/oxalica/rust-overlay/archive/master.tar.gz";
  pkgs = import <nixpkgs> {
    overlays = [
      (import rust-overlay)
      (_: prev: {
        my-rust-toolchain = prev.rust-bin.fromRustupToolchainFile ./toolchain.toml;
      })
    ];
  };
in
pkgs.callPackage (
  {
    mkShell,
    my-rust-toolchain,
  }:
  mkShell {
    strictDeps = true;
    nativeBuildInputs = [
      my-rust-toolchain
    ];
  }
) { }
</syntaxhighlight>
 
From https://ayats.org/blog/nix-rustup and https://github.com/oxalica/rust-overlay?tab=readme-ov-file#cheat-sheet-common-usage-of-rust-bin
 
=== Building Rust crates that require external system libraries ===
For example, the <code>openssl-sys</code> crate needs the OpenSSL static libraries and searches for the library path with <code>pkg-config</code>. That's why you need to have the Nix derivatives <code>openssl</code> and <code>pkg-config</code> in order to build that crate. You'll need to start a shell providing these packages:
<syntaxHighlight lang="shell-session">
$ nix-shell -p pkg-config openssl
$ nix-shell -p pkg-config openssl
</syntaxHighlight>
</syntaxHighlight>
In some cases (eg [https://discourse.nixos.org/t/rust-openssl-woes/12340 here]) you may also need
In some cases (eg [https://discourse.nixos.org/t/rust-openssl-woes/12340 here]) you may also need
<syntaxHighlight>
<syntaxHighlight lang="nix">
PKG_CONFIG_PATH = "${pkgs.openssl.dev}/lib/pkgconfig";
PKG_CONFIG_PATH = "${pkgs.openssl.dev}/lib/pkgconfig";
</syntaxHighlight>
</syntaxHighlight>
Similarly, the crate <code>libsqlite3-sys</code>, e.g. to use and compile the database ORM tool <code>diesel-cli</code> with Sqlite support, needs
<syntaxHighlight lang="shell-session">
$ nix-shell -p pkg-config sqlite
</syntaxHighlight>
Otherwise the following error occurs:
<syntaxHighlight lang="text">
error: linking with `cc` failed: exit status: 1
...
= note: /nix/store/kmqs0wll31ylwbqkpmlgbjrn6ny3myik-binutils-2.35.1/bin/ld: cannot find -lsqlite3
          collect2: error: ld returned 1 exit status
</syntaxHighlight>
Note that you need to use a <code>nix-shell</code> environment. Installing the Nix packages <code>openssl</code> or <code>sqlite</code> globally under <code>systemPackages</code> in NixOS or in <code>nix-env</code> [[FAQ/I installed a library but my compiler is not finding it. Why? | is discouraged]] and doesn't always work (<code>pkg-config</code> may not be able to locate the libraries).
=== Building with a different Rust version than the one in Nixpkgs ===
The following uses the [https://github.com/nix-community/fenix fenix] overlay and <code>makeRustPlatform</code> to build a crate with Rust Nightly:
<syntaxHighlight lang="nix">
{
  inputs = {
    fenix = {
      url = "github:nix-community/fenix";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    flake-utils.url = "github:numtide/flake-utils";
    nixpkgs.url = "nixpkgs/nixos-unstable";
  };
  outputs = { self, fenix, flake-utils, nixpkgs }:
    flake-utils.lib.eachDefaultSystem (system:
      let pkgs = nixpkgs.legacyPackages.${system}; in
      {
        defaultPackage = (pkgs.makeRustPlatform {
          inherit (fenix.packages.${system}.minimal) cargo rustc;
        }).buildRustPackage {
          pname = "hello";
          version = "0.1.0";
          src = ./.;
          cargoSha256 = nixpkgs.lib.fakeSha256;
        };
      });
}
</syntaxHighlight>
=== Using LLD instead of LD ===
If you want to use <code>lld</code>, then the correct way to do this is to use <code>pkgs.llvmPackages.bintools</code>, <em>not</em> <code>pkgs.lld</code>. This is because the former uses a wrapper script that correctly sets <code>rpath</code>. You can find more information about this [https://matklad.github.io/2022/03/14/rpath-or-why-lld-doesnt-work-on-nixos.html here].
[[Category:Languages]]

Latest revision as of 03:48, 15 November 2024

This article is about the Rust programming language. There are 3 methods to use the Rust compiler and toolchain in Nix/NixOS:

  1. via nixpkgs,
  2. via rustup,
  3. or with unofficial overlays on nixpkgs.

Installing via nixpkgs is the best 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. An advantage 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 switch 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.callPackage (
  {
    mkShell,
    cargo,
    rustc,
  }:
  mkShell {
    strictDeps = true;
    nativeBuildInputs = [
      cargo
      rustc
    ];
  }
) { }

Installating with bindgen support

By default crates using bindgen will not compile. To add bindegen support add the rustPlatform.bindegenHook to your nativeBuildInputs.

Here's an example shell.nix:

{
  pkgs ? import <nixpkgs> { },
}:
pkgs.callPackage (
  {
    mkShell,
    cargo,
    rustc,
    rustPlatform,
    pkg-config,
  }:
  mkShell {
    strictDeps = true;
    nativeBuildInputs = [
      cargo
      rustc
      rustPlatform.bindgenHook
      # optional: add pkg-config support
      pkg-config
    ];
    buildInputs = [
      # add desired native packages
      # ...
    ];
    # ...
  }
) { }

This also works, when compiling rust crates:

{
  rustPlatform,
  pkg-config,
  ...
}:
rustPlatform.buildRustPackage {
  # ...
  nativeBuildInputs = [
    rustPlatform.bindgenHook
    pkg-config
  ];
  buildInputs = [
    # add desired native packages
    # ...
  ];
}

Installation via rustup

The rustup tool is maintained by the Rust community and offers an 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 the most "normal" Rust experience I recommend using rustup with the following example shell.nix:

{
  pkgs ? import <nixpkgs> { },
}:
let
  overrides = (builtins.fromTOML (builtins.readFile ./rust-toolchain.toml));
in
pkgs.callPackage (
  {
    stdenv,
    mkShell,
    rustup,
    rustPlatform,
  }:
  mkShell {
    strictDeps = true;
    nativeBuildInputs = [
      rustup
      rustPlatform.bindgenHook
    ];
    # libraries here
    buildInputs =
      [
      ];
    RUSTC_VERSION = overrides.toolchain.channel;
    # https://github.com/rust-lang/rust-bindgen#environment-variables
    shellHook = ''
      export PATH="''${CARGO_HOME:-~/.cargo}/bin":"$PATH"
      export PATH="''${RUSTUP_HOME:-~/.rustup}/toolchains/$RUSTC_VERSION-${stdenv.hostPlatform.rust.rustcTarget}/bin":"$PATH"
    '';
  }
) { }

It's important to have a file named rust-toolchain.toml lying in the same directory as the shell.nix. Rust already has a standardized way of pinning a toolchain version for a workspace or a project. See the Rustup book for its syntax.

A minimal example of the rust-toolchain.toml:

[toolchain]
channel = "stable" # This can also be "nightly" if you want a nightly rust
                   # or nightly-20XX-XX-XX for a specific nightly.

Cross-compiling

To Windows via rustup

Unofficial overlays

  1. https://github.com/oxalica/rust-overlay (Flake support, Nightly & Stable)
  2. https://github.com/nix-community/fenix (Flake support, Nightly & Stable)
  3. https://github.com/mozilla/nixpkgs-mozilla (Flake support, Nightly & Stable)

devenv.sh support

  1. https://github.com/cachix/devenv/blob/main/examples/rust/devenv.nix and devenv shell

Developing Rust projects using Nix

The Nixpkgs manual uses buildRustPackage.

This blog post shows how to do it using dream2nix. A template repo is available here: https://github.com/srid/rust-nix-template

Using overrideAttrs with Rust Packages

nil = pkgs.nil.overrideAttrs (
  finalAttrs: previousAttrs: {
    version = "unstable-2024-09-19";
                                                                           
    src = pkgs.fetchFromGitHub {
      owner = "oxalica";
      repo = "nil";
      rev = "c8e8ce72442a164d89d3fdeaae0bcc405f8c015a";
      hash = "sha256-mIuOP4I51eFLquRaxMKx67pHmhatZrcVPjfHL98v/M8=";
    };
                                                                           
    # Requires IFD
    cargoDeps = pkgs.rustPlatform.importCargoLock {
      lockFile = finalAttrs.src + "/Cargo.lock";
      allowBuiltinFetchGit = true;
    };
    cargoHash = null;
                                                                           
    # Doesn't require IFD
    #cargoDeps = previousAttrs.cargoDeps.overrideAttrs {
    #  name = "nil-vendor.tar.gz";
    #  inherit (finalAttrs) src;
    #  #outputHash = pkgs.lib.fakeHash;
    #  outputHash = "sha256-RWgknkeGNfP2wH1X6nc+b10Qg1QX3UeewDdeWG0RIE8=";
    #};
  }
);

Packaging Rust projects with nix

At the time of writing, there are now no less than 8 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
crate2nix Codegen (with optional IFD) Many buildRustCrate experimental Spiritual successor to carnix
naersk Import 2 cargo Yes Seems to only support building on x86
cargo2nix Codegen Many cargo + custom Yes Defaults to the oxalica Rust overlay but this can be overridden with rustToolchain
import-cargo Import 1 cargo Unclear More of a proof of concept than a full working solution
crane Import 2 cargo Yes Inspired by naersk, with better support for composing Cargo invocations as completely separate derivations
dream2nix Codegen 1 or 2 cargo (via buildRustPackage or crane) Yes A framework for unifying 2nix converters across languages (Experimental)

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 dependencies, 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. buildRustPackage means it uses Nixpkgs' buildRustPackage, which in turn uses Cargo.
  • Supports cross: Does the solution allow for cross-compilation of crates?

Shell.nix example

{
  pkgs ? import <nixpkgs> { },
}:

pkgs.callPackage (
  {
    mkShell,
    rustc,
    cargo,
    rustPlatform,
    rustfmt,
    clippy,
    rust-analyzer,
  }:
  mkShell {
    strictDeps = true;
    nativeBuildInputs = [
      rustc
      cargo
      rustfmt
      clippy
      rust-analyzer
    ];

    # Certain Rust tools won't work without this
    # rust-analyzer from nixpkgs does not need 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 = "${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.

Custom Rust version or targets

let
  rustVersion = "latest";
  #rustVersion = "1.62.0";
  rust_overlay = import (
    builtins.fetchTarball "https://github.com/oxalica/rust-overlay/archive/master.tar.gz"
  );
  pkgs = import <nixpkgs> {
    overlays = [
      rust_overlay
      (_: prev: {
        my-rust = prev.rust-bin.stable.${rustVersion}.default.override {
          extensions = [
            "rust-src" # for rust-analyzer
            "rust-analyzer"
          ];
          # Or import nixpkgs with `crossSystem`
          #targets = [ "arm-unknown-linux-gnueabihf" ];
        };
      })
    ];

  };
in
pkgs.callPackage (
  {
    mkShell,
    hello,
    my-rust,
    pkg-config,
    openssl,
  }:
  mkShell {
    strictDeps = true;
    # host/target agnostic programs
    depsBuildBuild = [
      hello
    ];
    # compilers & linkers & dependecy finding programs
    nativeBuildInputs = [
      my-rust
      pkg-config
    ];
    # libraries
    buildInputs = [
      openssl
    ];
    RUST_BACKTRACE = 1;
  }
) { }

VSCode integration

The rust-lang.rust-analyzer VSCode extension offers Rust support.

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.

FAQ

Rust from a rustup toolchain file

let
  rust-overlay = builtins.fetchTarball "https://github.com/oxalica/rust-overlay/archive/master.tar.gz";
  pkgs = import <nixpkgs> {
    overlays = [
      (import rust-overlay)
      (_: prev: {
        my-rust-toolchain = prev.rust-bin.fromRustupToolchainFile ./toolchain.toml;
      })
    ];
  };
in
pkgs.callPackage (
  {
    mkShell,
    my-rust-toolchain,
  }:
  mkShell {
    strictDeps = true;
    nativeBuildInputs = [
      my-rust-toolchain
    ];
  }
) { }

From https://ayats.org/blog/nix-rustup and https://github.com/oxalica/rust-overlay?tab=readme-ov-file#cheat-sheet-common-usage-of-rust-bin

Building Rust crates that require external system libraries

For example, the openssl-sys crate needs the OpenSSL static libraries and searches for the library path with pkg-config. That's why you need to have the Nix derivatives openssl and pkg-config in order to build that crate. You'll need to 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";

Similarly, the crate libsqlite3-sys, e.g. to use and compile the database ORM tool diesel-cli with Sqlite support, needs

$ nix-shell -p pkg-config sqlite

Otherwise the following error occurs:

error: linking with `cc` failed: exit status: 1
...
 = note: /nix/store/kmqs0wll31ylwbqkpmlgbjrn6ny3myik-binutils-2.35.1/bin/ld: cannot find -lsqlite3
          collect2: error: ld returned 1 exit status

Note that you need to use a nix-shell environment. Installing the Nix packages openssl or sqlite globally under systemPackages in NixOS or in nix-env is discouraged and doesn't always work (pkg-config may not be able to locate the libraries).

Building with a different Rust version than the one in Nixpkgs

The following uses the fenix overlay and makeRustPlatform to build a crate with Rust Nightly:

{
  inputs = {
    fenix = {
      url = "github:nix-community/fenix";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    flake-utils.url = "github:numtide/flake-utils";
    nixpkgs.url = "nixpkgs/nixos-unstable";
  };

  outputs = { self, fenix, flake-utils, nixpkgs }:
    flake-utils.lib.eachDefaultSystem (system:
      let pkgs = nixpkgs.legacyPackages.${system}; in
      {
        defaultPackage = (pkgs.makeRustPlatform {
          inherit (fenix.packages.${system}.minimal) cargo rustc;
        }).buildRustPackage {
          pname = "hello";
          version = "0.1.0";
          src = ./.;
          cargoSha256 = nixpkgs.lib.fakeSha256;
        };
      });
}

Using LLD instead of LD

If you want to use lld, then the correct way to do this is to use pkgs.llvmPackages.bintools, not pkgs.lld. This is because the former uses a wrapper script that correctly sets rpath. You can find more information about this here.