Rust: Difference between revisions
imported>TenTypekMatus m Add some fixes, notably with devenv.sh |
Add tip for using rust-analyzer from nixpkgs with the VS Code extension |
||
| (23 intermediate revisions by 13 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 best 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. 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. | 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"> | ||
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.callPackage ( | ||
{ | |||
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 bindgen support add the <code>rustPlatform.bindgenHook</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: | |||
<syntaxHighlight lang="nix"> | |||
{ | |||
rustPlatform, | |||
pkg-config, | |||
... | |||
}: | |||
rustPlatform.buildRustPackage { | |||
# ... | |||
nativeBuildInputs = [ | |||
rustPlatform.bindgenHook | |||
pkg-config | |||
]; | |||
buildInputs = [ | |||
# add desired native packages | |||
# ... | |||
]; | |||
} | } | ||
</syntaxHighlight> | </syntaxHighlight> | ||
| Line 28: | Line 96: | ||
If you want 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"> | ||
{ pkgs ? import <nixpkgs> {} }: | { | ||
pkgs. | pkgs ? import <nixpkgs> { }, | ||
}: | |||
let | |||
overrides = (builtins.fromTOML (builtins.readFile ./rust-toolchain.toml)); | |||
in | |||
pkgs.callPackage ( | |||
{ | |||
stdenv, | |||
mkShell, | |||
rustup, | |||
rustPlatform, | |||
}: | |||
mkShell { | |||
strictDeps = true; | |||
nativeBuildInputs = [ | |||
rustup | rustup | ||
rustPlatform.bindgenHook | |||
]; | ]; | ||
RUSTC_VERSION = | # 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 | ||
shellHook = '' | shellHook = '' | ||
export PATH= | export PATH="''${CARGO_HOME:-~/.cargo}/bin":"$PATH" | ||
export PATH= | export PATH="''${RUSTUP_HOME:-~/.rustup}/toolchains/$RUSTC_VERSION-${stdenv.hostPlatform.rust.rustcTarget}/bin":"$PATH" | ||
''; | |||
} | |||
) { } | |||
</syntaxhighlight> | |||
</ | |||
It's important to have a file named <code>rust-toolchain</code> lying in the same directory as the shell.nix. | 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> | |||
== Cross-compiling == | == Cross-compiling == | ||
| Line 80: | Line 148: | ||
* [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/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] | ** [https://github.com/jraygauthier/jrg-rust-cross-experiment/blob/master/simple-static-rustup-target-windows/shell.nix shell.nix] | ||
=== To Windows via a cargo plugin: === | |||
* use [https://crates.io/crates/cargo-xwin cargo-xwin] with rustup install or the [https://search.nixos.org/packages?show=cargo-xwin&type=packages&query=cargo+windows nix cargo plugin] | |||
* run cargo commands prefixed by xwin, e.g. <code>cargo xwin run --target x86_64-pc-windows-msvc</code> | |||
== Unofficial overlays == | == Unofficial overlays == | ||
| Line 90: | Line 163: | ||
# https://github.com/cachix/devenv/blob/main/examples/rust/devenv.nix and <code>devenv shell</code> | # https://github.com/cachix/devenv/blob/main/examples/rust/devenv.nix and <code>devenv shell</code> | ||
== Developing Rust projects using Nix == | == Developing Rust projects using Nix == | ||
| Line 103: | Line 171: | ||
== Using overrideAttrs with Rust Packages == | == Using overrideAttrs with Rust Packages == | ||
There are two ways to use <code>overrideAttrs</code> with Rust packages: | |||
[ | * Using [[Import From Derivation]]: | ||
<p> | |||
== | <syntaxhighlight lang="nix"> | ||
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; | |||
} | |||
); | |||
</syntaxhighlight> | |||
</p> | |||
* Overriding <code>cargoDeps</code>: | |||
<p> | |||
<syntaxhighlight lang="nix"> | |||
nil = pkgs.nil.overrideAttrs ( | |||
finalAttrs: previousAttrs: { | |||
version = "unstable-2024-09-19"; | |||
src = pkgs.fetchFromGitHub { | |||
owner = "oxalica"; | |||
repo = "nil"; | |||
rev = "c8e8ce72442a164d89d3fdeaae0bcc405f8c015a"; | |||
hash = "sha256-mIuOP4I51eFLquRaxMKx67pHmhatZrcVPjfHL98v/M8="; | |||
}; | |||
# Doesn't require IFD | |||
cargoDeps = previousAttrs.cargoDeps.overrideAttrs { | |||
name = "nil-vendor.tar.gz"; | |||
inherit (finalAttrs) src; | |||
#outputHash = pkgs.lib.fakeHash; | |||
outputHash = "sha256-RWgknkeGNfP2wH1X6nc+b10Qg1QX3UeewDdeWG0RIE8="; | |||
#}; | |||
} | |||
); | |||
</syntaxhighlight> | |||
</p> | |||
== Packaging Rust projects with nix == | == Packaging Rust projects with nix == | ||
| Line 126: | Line 227: | ||
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: | 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: | ||
{| | {| class="wikitable" style="margin:auto" | ||
|- | |- | ||
| [https://github.com/NixOS/nixpkgs/blob/ | ! Name !! Cargo.lock solution !! Derivations !! Build logic !! Supports cross !! Notes | ||
|- | |||
| <code>[https://github.com/NixOS/nixpkgs/blob/master/doc/languages-frameworks/rust.section.md buildRustPackage]</code> | |||
| Checksum | | Checksum | ||
| 1 | | 1 | ||
| Line 176: | Line 273: | ||
| 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] | | 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:// | | [https://dream2nix.dev/reference/rust-crane <code>dream2nix</code>] | ||
| Codegen | | Codegen | ||
| 1 or 2 | | 1 or 2 | ||
| cargo (via <code>buildRustPackage</code> or <code>crane</code>) | | cargo (via <code>buildRustPackage</code> or <code>crane</code>) | ||
| Yes | | Yes | ||
| A framework for unifying 2nix converters across languages | | A framework for unifying 2nix converters across languages (Experimental) | ||
|} | |} | ||
| Line 192: | Line 289: | ||
== Shell.nix example == | == Shell.nix example == | ||
< | <syntaxhighlight lang="nix"> | ||
{ pkgs ? import <nixpkgs> {} }: | { | ||
pkgs.mkShell { | 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}"; | ||
</ | } | ||
) { } | |||
</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. | 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 === | === Custom Rust version or targets === | ||
< | <syntaxhighlight lang="nix"> | ||
let | let | ||
rustVersion = "latest"; | rustVersion = "latest"; | ||
#rustVersion = "1.62.0"; | #rustVersion = "1.62.0"; | ||
rust = | 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 | in | ||
pkgs.mkShell { | 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> | |||
=== VS Code integration === | |||
The [https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer] VS Code extension offers Rust support. | |||
If you get the error <code>can't load standard library, try installing rust-src</code>, either configure <code>RUST_SRC_PATH</code> as shown above, or set <code>"rust-analyzer.server.path": "rust-analyzer"</code> in your VS Code settings to use rust-analyzer from nixpkgs rather than the one bundled with the extension. | |||
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. | 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 == | ||
=== Rust from a rustup toolchain file === | |||
<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 === | === 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: | 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: | ||
| Line 291: | Line 439: | ||
</syntaxHighlight> | </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> [ | 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 === | === 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 | 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"> | <syntaxHighlight lang="nix"> | ||
| Line 329: | Line 477: | ||
[[Category:Languages]] | [[Category:Languages]] | ||
[[Category:Rust]] | |||