dcf3a897d1
Rust-on-NixOS knowledge plugin. Relocates plugin.json into .claude-plugin/ so it loads as a proper plugin, and adds README/LICENSE/.gitignore for distribution. Skill rust-nix-toolchain (Cranelift vs LLVM codegen, fenix nightly pins, cargo/clippy gates) with cranelift-limitations reference.
4.3 KiB
4.3 KiB
name, description
| name | description |
|---|---|
| rust-nix-toolchain | This skill should be used when the user encounters "asm! and global_asm! sym operands are not yet supported", "wasmtime-fiber" compilation failures, "cargo test fails but cargo check works", "cranelift codegen backend" issues, "update Rust nightly on NixOS", "fenix toolchain pin", or mentions Rust codegen backend problems on NixOS. Covers the Cranelift vs LLVM codegen backend split, NixOS fenix toolchain management, and common Rust nightly pitfalls. |
Rust Nightly Toolchain on NixOS
Cranelift vs LLVM Codegen Backend
NixOS dev setups commonly use codegen-backend = "cranelift" in ~/.cargo/config.toml for faster dev builds. Cranelift does NOT support global_asm! { sym } operands. This causes wasmtime-fiber and other crates using global_asm! with sym to fail.
Symptom
error: asm! and global_asm! sym operands are not yet supported
--> wasmtime-fiber-28.0.1/src/stackswitch/x86_64.rs
Why cargo check works but cargo test fails
cargo check/cargo clippyonly verify types and borrow checking — no codegencargo test/cargo buildinvoke the codegen backend, hitting the Cranelift limitation- This makes the error appear only at test/build time, not during linting
Fix: Override codegen backend for tests
# One-shot: force LLVM for this test run
CARGO_PROFILE_DEV_CODEGEN_BACKEND=llvm cargo test --lib
# Or permanently in the project's .cargo/config.toml:
# [profile.dev]
# codegen-backend = "llvm"
Where the Cranelift backend is configured
Check ~/.cargo/config.toml:
[profile.dev]
codegen-backend = "cranelift" # faster codegen for dev builds
[unstable]
codegen-backend = true
This is a user-level config that affects all projects. Project-level .cargo/config.toml can override it.
NixOS Fenix Toolchain Management
How the Rust nightly is pinned
On NixOS with fenix, the Rust nightly version is determined by:
flake.nixdeclares fenix input:fenix.url = "github:nix-community/fenix"flake.lockpins a specific fenix commit, which bundles nightly manifests- NixOS module (e.g.
nixos/rust-dev.nix) selects components:
rust-nightly = fenix.packages.${pkgs.stdenv.hostPlatform.system}.complete.withComponents [
"cargo" "clippy" "rustc" "rustfmt"
"rust-src" "rust-analyzer"
"rustc-codegen-cranelift-preview"
];
Updating the nightly
cd ~/projects/servers/<hostname>
nix flake update fenix
sudo nixos-rebuild switch --flake '.#<hostname>'
This pulls the latest fenix commit which includes the most recent nightly.
Pinning a specific nightly date
Replace complete.withComponents with toolchainOf:
rust-nightly = fenix.packages.${system}.toolchainOf {
channel = "nightly";
date = "2026-03-15";
sha256 = "<manifest-hash>"; # required for pure evaluation
}.withComponents [ "cargo" "clippy" "rustc" "rustfmt" "rust-src" ];
Cargo Quality Gate
Standard Rust quality gate sequence:
# 1. Format
cargo fmt
# 2. Lint (zero warnings required)
cargo clippy --all --benches --tests --examples --all-features
# 3. Test (use LLVM if Cranelift is default)
CARGO_PROFILE_DEV_CODEGEN_BACKEND=llvm cargo test --lib
Common clippy patterns
| Warning | Fix |
|---|---|
collapsible_match / collapsible_if |
Merge nested if into match guard: Variant if condition => { ... } |
new_without_default |
Add impl Default for T { fn default() -> Self { Self::new() } } |
let_unit_value |
Remove let _ = from expressions returning () |
unnecessary_sort_by |
Use `sort_by_key( |
derivable_impls |
Replace manual impl Default with #[derive(Default)] |
manual_map |
Replace if let Some(x) = opt { Some(f(x)) } else { None } with opt.map(f) |
useless_conversion |
Remove redundant .into_iter() on values already implementing IntoIterator |
Feature-gated fields in tests
When a struct has #[cfg(feature = "...")] fields, tests compiled with --all-features must include those fields:
let cfg = MyConfig {
normal_field: value,
#[cfg(feature = "discord")]
discord: None, // must be present when feature is active
};