Hedronite Lesson · Polyglot-Dev / Nix · Sun 2026-10-04

Packaging an unsafe-macros crate derivation, devShell, flake check

Beat A named the five unsafe unlocks and a macro_rules! matcher. Beat B puts that binary behind a store path, a shell, and a check.

Lesson Class: Duha (Nix packaging shell · Beat B · topic T1)
Focus: buildRustPackage · cargoLock/cargoHash · mkShell · flake checks
Code Blocks: clean blocks from rust-tops corpus + unsafe-macros-demo sketch
Done-criteria: Sketch buildRustPackage, open a Rust devShell, name one checks entry
Grounding: rust-tops cargo-tops.nix + flake.nix · NIX-SYLLABUS #16/#19 framing only · Asr Pills untouched · no flake check claimed
The Crate Derivation
buildRustPackage for unsafe-macros-demo: src, lock pin, $out/bin.
The Dev Shell
devShells.default holds rustc/cargo for edit; keep them out of the release closure.
The Flake Check
checks.unsafe-macros-demo is what nix flake check would build. Not run this fire.
Beat A named the five unsafe unlocks and a macro_rules! matcher. Beat B puts that binary behind a store path, a shell, and a check.

<!-- hal:authoritative:yaml -->

*Beat A named the five unsafe unlocks and a macro_rules! matcher. Beat B puts that binary behind a store path, a shell, and a check.*

§I - Frame

Duha Beat B, Nix packaging session 06. Beat A (Rust session 23) taught the Five Unlocks, the Safe Wrapper, and the Matcher from TRPL Ch.20. This shell packages a small crate that exercises those moves: one derivation that builds the binary, one devShell that holds the toolchain, one checks entry that nix flake check can build.

Session 05 packaged the patterns crate. Same three packaging moves, new crate noun: an unsafe-macros teaching binary, not another patterns demo. Asr owns the Pills cursor and the later Rust-roads rows (#16 crate-as-derivation, #19 flake devShell/checks). Those Done cells stay unmarked. This bundle does not consume an Asr NIX-SYLLABUS row and does not consume a Pill.

Three moves land by the end:

  1. The Crate Derivation: read rustPlatform.buildRustPackage for an unsafe-macros demo binary (src, lock hash, what lands in $out).
  2. The Dev Shell: name devShells.default as the editable toolchain, not the release store path.
  3. The Flake Check: say what nix flake check builds from the checks attribute set.

Done-criteria: Can sketch a buildRustPackage for an unsafe-macros binary, open a Rust devShell, and name one flake checks entry that proves the build.

§II - The Crate Derivation

A derivation is still a build spec: inputs in, store path out. For Rust, nixpkgs wraps that as rustPlatform.buildRustPackage. The rust-tops corpus shows the shape in nix/cargo-tops.nix (read-only teach from the repo; no bot build cadence):

{
  lib,
  rustPlatform,
}:

rustPlatform.buildRustPackage {
  pname = "cargo-tops";
  version = "0.1.3";

  src = lib.cleanSource ../.;

  cargoLock.lockFile = ../Cargo.lock;

  buildAndTestSubdir = "crates/cargo-tops";

  doCheck = true;

  meta = {
    description = "init / check / gate for the Rust-TOPS protocol";
    mainProgram = "cargo-tops";
  };
}

Read it the way NIX-SYLLABUS row #16 asks, as framing only: src is the crate tree; cargoLock.lockFile (or cargoHash when you vendor differently) pins the crate graph; buildAndTestSubdir selects a workspace member; doCheck runs cargo test in the sandbox; the binary lands under $out (usually $out/bin/<mainProgram>). The live manual section is nixpkgs' Rust chapter (buildRustPackage, cargoLock, cargoHash) until a local HTML copy exists.

For today's lesson the same skeleton fits a single-binary crate named unsafe-macros-demo. Its main dereferences one raw pointer inside a small unsafe block and expands a macro_rules! arm of the form ( $( $x:expr ),* ), then prints the pointer value and the expanded list. Swap pname to unsafe-macros-demo, point src at that crate, and keep a real Cargo.lock in tree. The demo can stay on std alone. No network fetches during nix build. Prefer cargoLock.lockFile when the tree has a lock; use cargoHash only when that is the pin style you keep.

Sandbox honesty still rules. Flake inputs and the lock hash must explain every byte that enters the builder. A print-and-exit binary proves the Five Unlocks and the Matcher without external services. Do not copy the rust-tops buildAndTestSubdir unless this crate is a workspace member. A single package leaves that field out.

§III - The Dev Shell

A release derivation is not a workspace. While you edit the unsafe-macros crate you want rustc, cargo, clippy, and rustfmt on PATH without baking them into the binary's runtime closure. That is devShells:

devShells = forAllSystems (
  system:
  let
    pkgs = pkgsFor system;
  in
  {
    default = pkgs.mkShell {
      packages = [
        pkgs.rustc
        pkgs.cargo
        pkgs.clippy
        pkgs.rustfmt
      ];
    };
  }
);

The rust-tops flake.nix uses that pattern (plus cargo-tops itself on the shell). Enter with nix develop. Build the release path separately with nix build .#unsafe-macros-demo. Mixing those two jobs is how a toolchain becomes a runtime dependency by accident.

The raw-pointer dereference and the macro_rules! matcher live in the Rust sources, not in the Nix shell. The shell only gives a pinned compiler so cargo test matches what the derivation will see. devShells.default is the edit path. It is not $out.

§IV - The Flake Check

packages is what you install. checks is what you ask Nix to build for proof. From the same corpus flake:

checks = forAllSystems (
  system:
  let
    pkgs = pkgsFor system;
  in
  {
    cargo-tops = self.packages.${system}.cargo-tops;
  }
);

nix flake check builds every attribute under checks for the current system. For this crate, one entry is enough:

unsafe-macros-demo = self.packages.${system}.unsafe-macros-demo;

That attribute proves the derivation evaluates and builds, including cargo test when doCheck is true. A tighter check can runCommand the binary and assert it prints the pointer value and the macro's expanded list. This lesson does not claim that check was run. nix is on the lab Mac PATH. No throwaway nix flake check was executed for this fire, so there is no exit code to report as green.

Three outputs, three jobs: packages ships, devShells.default edits, checks.unsafe-macros-demo proves. That is the packaging shell for Beat A's unsafe-macros crate.

§V - Proof and close

  1. Copy the buildRustPackage skeleton, rename pname to unsafe-macros-demo, and say what cargoLock.lockFile pins.
  2. Open a mkShell with rustc and cargo. State why those packages are not runtime inputs of the release derivation.
  3. Add checks.unsafe-macros-demo and say what nix flake check will build.
  4. Name which Asr NIX-SYLLABUS rows teach the same moves later (#16, #19) and confirm this Duha bundle did not mark them.

Done-criteria: Can sketch a buildRustPackage for an unsafe-macros binary, open a Rust devShell, and name one flake checks entry that proves the build.

Asr keeps the next unmarked Pill and the Rust-roads rows. Duha Beat B only packages today's T1 crate.

Related