Jump to content

Rust: Difference between revisions

12,368 bytes added ,  29 April
Remove Rust Nightlies section. This section had no information that wasn't found on the rest of the page (only having two bullet points referring to other sections of the page). Instead, modify the page to elaborate more in the respective sections.
imported>Efx
m (Corrected the example per https://discourse.nixos.org/t/openssl-dependency-for-rust/3186/4)
(Remove Rust Nightlies section. This section had no information that wasn't found on the rest of the page (only having two bullet points referring to other sections of the page). Instead, modify the page to elaborate more in the respective sections.)
 
(79 intermediate revisions by 43 users not shown)
Line 1: Line 1:
[[Category:Languages]]
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 rustup,
# 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.


This article is about the [https://www.rust-lang.org rust programming language].
== Installing via nixpkgs ==
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.  


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


Either use [https://nixos.org/nixpkgs/manual/#using-the-rust-nightlies-overlay rust overlay] or rustup to get install rust nightlies.
<syntaxHighlight lang="nix">
let
  # Pinned nixpkgs, deterministic. Last updated: 2/12/21.
  pkgs = import (fetchTarball("https://github.com/NixOS/nixpkgs/archive/a58a0b5098f0c2a389ee70eb69422a052982d990.tar.gz")) {};


== Neovim Completion ==
  # Rolling updates, not deterministic.
  # pkgs = import (fetchTarball("channel:nixpkgs-unstable")) {};
in pkgs.mkShell {
  buildInputs = [ pkgs.cargo pkgs.rustc ];
}
</syntaxHighlight>


Racer completion can be configured using the following snippet:
== 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 <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 the most "normal" Rust experience I recommend using rustup with the following example shell.nix:
<syntaxHighlight lang="nix">
<syntaxHighlight lang="nix">
(neovim.override {
{ pkgs ? import <nixpkgs> {} }:
   configure = {
   let
     customRC = ''
     overrides = (builtins.fromTOML (builtins.readFile ./rust-toolchain.toml));
      if filereadable($HOME . "/.vimrc")
    libPath = with pkgs; lib.makeLibraryPath [
        source ~/.vimrc
       # load external libraries that you need in your rust project here
      endif
     ];
      let $RUST_SRC_PATH = '${stdenv.mkDerivation {
in
        inherit (rustc) src;
  pkgs.mkShell rec {
        inherit (rustc.src) name;
     buildInputs = with pkgs; [
        phases = ["unpackPhase" "installPhase"];
       clang
        installPhase = "cp -r src $out";
      # Replace llvmPackages with llvmPackages_X, where X is the latest LLVM version (at the time of writing, 16)
       }}'
      llvmPackages.bintools
     '';
       rustup
     packages.nixbundle.start = with vimPlugins; [
       nvim-completion-manager
       nvim-cm-racer
     ];
     ];
   };
    RUSTC_VERSION = overrides.toolchain.channel;
})
    # https://github.com/rust-lang/rust-bindgen#environment-variables
    LIBCLANG_PATH = pkgs.lib.makeLibraryPath [ pkgs.llvmPackages_latest.libclang.lib ];
    shellHook = ''
      export PATH=$PATH:''${CARGO_HOME:-~/.cargo}/bin
      export PATH=$PATH:''${RUSTUP_HOME:-~/.rustup}/toolchains/$RUSTC_VERSION-x86_64-unknown-linux-gnu/bin/
      '';
    # Add precompiled library to rustc search path
    RUSTFLAGS = (builtins.map (a: ''-L ${a}/lib'') [
      # add libraries here (e.g. pkgs.libvmi)
    ]);
    LD_LIBRARY_PATH = libPath;
    # Add glibc, clang, glib, and other headers to bindgen search path
    BINDGEN_EXTRA_CLANG_ARGS =
    # Includes normal include path
    (builtins.map (a: ''-I"${a}/include"'') [
      # add dev libraries here (e.g. pkgs.libvmi.dev)
      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/''
    ];
  }
</syntaxHighlight>
 
It's important to have a file named <code>rust-toolchain.toml</code> 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 [https://rust-lang.github.io/rustup/overrides.html#the-toolchain-file the Rustup book] for its syntax.
 
A minimal example of the <code>rust-toolchain.toml</code>:
<syntaxhighlight lang="toml">
[toolchain]
channel = "stable" # This can also be "nightly" if you want a nightly rust
                  # or nightly-20XX-XX-XX for a specific nightly.
</syntaxhighlight>
 
The important 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 <code>BINDGEN_EXTRA_CLANG_ARGS</code> and <code>RUSTFLAGS</code>
 
== Cross-compiling ==
 
=== To Windows via rustup ===
 
* [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 ==
 
# 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 (Flake support, Nightly & Stable)
 
== devenv.sh support ==
 
# https://github.com/cachix/devenv/blob/main/examples/rust/devenv.nix and <code>devenv shell</code>
 
== Developing Rust projects using Nix ==
 
The [https://nixos.org/manual/nixpkgs/stable/#rust Nixpkgs manual] uses <code>buildRustPackage</code>.
 
[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
 
== Using overrideAttrs with Rust Packages ==
 
[https://discourse.nixos.org/t/is-it-possible-to-override-cargosha256-in-buildrustpackage/4393/7 This does not seem to be possible.]
 
== Using overrideArgs with Rust Packages ==
 
This is a bit tricky, you can't just use <code>overrideArgs</code>.  [https://discourse.nixos.org/t/is-it-possible-to-override-cargosha256-in-buildrustpackage/4393/3 Here] is one example of how to do it.  The trick is to use two nested calls to <code>overrideAttrs</code>; the outer call overrides the <code>cargoDeps</code> attribute, the inner call rebuilds the vendored tarball and provides the updated hash:
 
<syntaxHighlight lang="nix">
overlays = [
   (final: prev: {
    some-nixpkgs-package = prev.some-nixpkgs-package.overrideAttrs (oldAttrs: {
      cargoDeps = oldAttrs.cargoDeps.overrideAttrs (_: {
        # ...
      });
    });
  })
];
</syntaxHighlight>
</syntaxHighlight>


== Emacs integration ==
== Packaging Rust projects with nix ==
Making Emacs use certain version of libraries can be tricky, in particular for example OpenSSL might not be found ''even'' if it is in your Nix store. Two solutions exists :
 
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
|-
| [https://github.com/NixOS/nixpkgs/blob/4fc53b59aecbc25c0e173163d60155f8fca14bfd/doc/languages-frameworks/rust.section.md <code>buildRustPackage</code>]
| Checksum
| 1
| cargo
| Yes
| Built into nixpkgs
|-
| [https://github.com/kolloch/crate2nix <code>crate2nix</code>]
| Codegen (with optional IFD)
| Many
| <code>buildRustCrate</code>
| [https://github.com/kolloch/crate2nix/commit/8bfeb42bda097e0bdf5452691a5e157aad3cc11f experimental]
| Spiritual successor to [https://github.com/nix-community/carnix <code>carnix</code>]
|-
| [https://github.com/nmattia/naersk/ <code>naersk</code>]
| Import
| 2
| cargo
| Yes
| [https://github.com/nmattia/naersk/blob/22b96210b2433228d42bce460f3befbdcfde7520/rust/rustc.nix#L22-L29 Seems to only support building on x86]
|-
| [https://github.com/cargo2nix/cargo2nix <code>cargo2nix</code>]
| Codegen
| Many
| cargo + custom
| Yes
| 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>]
| Import
| 1
| cargo
| Unclear
| 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://github.com/nix-community/dream2nix/blob/main/docs/src/subsystems/rust.md <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
|}
 
Explanation for the columns


* a "global" one
* '''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.
* a per-project one
* '''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.  <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?


Both solution relies on the usage of a library called [https://github.com/direnv/direnv/wiki/Nix direnv] and its package integration in Emacs [https://github.com/wbolster/emacs-direnv emacs-direnv].
== Shell.nix example ==
<syntaxHighlight lang="nix">
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
  nativeBuildInputs = with pkgs; [ rustc cargo gcc rustfmt clippy ];


=== Global solution ===
  # Certain Rust tools won't work without this
If you happen to have all of your Rust projects in one folder "<code>/path/to/your/rust</code>" then placing a file <code>/path/to/your/rust/.envrc</code> with this content :
  # This can also be fixed by using oxalica/rust-overlay and specifying the rust-src extension
<syntaxHighlight lang="shell">
  # 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.
use_nix
  RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}";
}
</syntaxHighlight>
</syntaxHighlight>
and another file <code>/path/to/your/rust/default.nix</code> :
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.
<syntaxHighlight lang="Nix">
 
=== Custom Rust version ===
 
<syntaxHighlight lang="nix">
/*
based on
https://discourse.nixos.org/t/how-can-i-set-up-my-rust-programming-environment/4501/9
*/
let
let
   moz_overlay = import (builtins.fetchTarball https://github.com/mozilla/nixpkgs-mozilla/archive/master.tar.gz);
   rust_overlay = import (builtins.fetchTarball "https://github.com/oxalica/rust-overlay/archive/master.tar.gz");
   nixpkgs = import <nixpkgs> { overlays = [ moz_overlay ]; };
   pkgs = import <nixpkgs> { overlays = [ rust_overlay ]; };
   ruststable = (nixpkgs.latest.rustChannels.stable.rust.override { extensions = [ "rust-src" "rls-preview" "rust-analysis" "rustfmt-preview" ];});
   rustVersion = "latest";
  #rustVersion = "1.62.0";
  rust = pkgs.rust-bin.stable.${rustVersion}.default.override {
    extensions = [
      "rust-src" # for rust-analyzer
      "rust-analyzer"
    ];
  };
in
in
   with nixpkgs;
pkgs.mkShell {
   stdenv.mkDerivation {
  buildInputs = [
     name = "rust";
    rust
     buildInputs = [ openssl pkgconfig nasm rustup ruststable cmake zlib ];
   ] ++ (with pkgs; [
   }
    pkg-config
    # other dependencies
    #gtk3
    #wrapGAppsHook
  ]);
   RUST_BACKTRACE = 1;
}
</syntaxHighlight>
 
=== VSCode integration ===
The [https://marketplace.visualstudio.com/items?itemName=rust-lang.rust rust-lang.rust] and [https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer rust-lang.rust-analyzer] VSCode extensions offer Rust support.
 
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.
 
== FAQ ==
=== 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
</syntaxHighlight>
In some cases (eg [https://discourse.nixos.org/t/rust-openssl-woes/12340 here]) you may also need
<syntaxHighlight lang="nix">
PKG_CONFIG_PATH = "${pkgs.openssl.dev}/lib/pkgconfig";
</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>
</syntaxHighlight>


will, for example, grant you a free acces to OpenSSL ! Feel free to override this to your liking !
=== Using LLD instead of LD ===


=== Per-project solution ===
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].
For a more granular solution to this problem a more in-depth review of [https://github.com/direnv/direnv/wiki/Nix#persistent-shell-for-speeding-things-up the direnv wiki] will be poorly be needed !
 
[[Category:Languages]]
3

edits