Files
claude-plugin-rust-craft/skills/rust-nix-toolchain/SKILL.md
T
Oleks dcf3a897d1 Initial commit: rust-craft plugin
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.
2026-06-01 11:28:48 +03:00

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 clippy only verify types and borrow checking — no codegen
  • cargo test / cargo build invoke 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:

  1. flake.nix declares fenix input: fenix.url = "github:nix-community/fenix"
  2. flake.lock pins a specific fenix commit, which bundles nightly manifests
  3. 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
};