Hedronite Lesson · Polyglot-Dev / Nix · Thu 2026-10-01

Packaging a trait-objects crate derivation, devShell, flake check

Beat A taught dyn Trait. 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 + trait-objects 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
The Crate Derivation
buildRustPackage: src, lock pin, $out/bin — same noun as Pill 6, Rust road shape.
The Dev Shell
devShells hold rustc/cargo for edit; keep them out of the release closure.
The Flake Check
checks attrs are what nix flake check builds; put the package there for proof.
Beat A taught dyn Trait. Beat B puts that binary behind a store path, a shell, and a check.

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

Beat A taught dyn Trait. Beat B puts that binary behind a store path, a shell, and a check.

§I - Frame

Duha Beat B, Nix packaging session 04. Beat A (Rust session 21) taught the Object Ledger, Box<dyn Draw>, and an open collection of trait objects. This shell packages a small crate that uses those moves: one derivation that builds the binary, one devShell that holds the toolchain, one checks entry that nix flake check can run.

Session 03 packaged yesterday's async/await crate. Same three packaging moves, new crate noun. Asr owns the Pills cursor and the later Rust-roads rows (#16 crate-as-derivation, #19 flake devShell/checks). Those Done cells stay unmarked today. This bundle does not consume a Pill.

Three moves land by the end:

  1. The Crate Derivation: read rustPlatform.buildRustPackage for a trait-objects 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 a trait-objects 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: 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>).

For today's trait-objects lesson, the same skeleton fits a single-binary crate whose main builds a Vec<Box<dyn Draw>>, calls draw on each entry, and prints. Swap pname to something like trait-objects-demo, point src at that crate, keep a real Cargo.lock in tree. The demo can stay on std alone: a Draw trait, two implementors, and a loop. No GUI toolkit, no gray-area crate in the lock. 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 dyn wire without needing display servers or network fetches during nix build.

§III - The Dev Shell

A release derivation is not a workspace. While you edit the trait-objects 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 .#trait-objects-demo (or whatever you named the package). Mixing those two jobs is how people ship a toolchain as a runtime dependency by accident.

Dynamic dispatch lives in the Rust binary at draw call sites, not in the Nix shell. The shell only gives you a pinned compiler so cargo run and cargo test match what the derivation will see.

§IV - The Flake Check

packages is what you install. checks is what CI (and 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;
    # ... other proofs ...
  }
);

nix flake check builds every attribute under checks for the current system. Putting the trait-objects package itself in checks proves the derivation evaluates and builds. A tighter check can runCommand the binary and assert it prints both implementor lines, still without standing up a second lesson store of tests outside the flake.

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

§V - Proof and close

  1. Copy the buildRustPackage skeleton, rename it for a trait-objects binary, and say what cargoLock.lockFile pins.
  2. Open a mkShell with rustc/cargo and state why those packages are not buildInputs of the release derivation.
  3. Add the package to checks 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 a trait-objects binary, open a Rust devShell, and name one flake checks entry that proves the build.

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

Related