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.
<!-- 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:
- The Crate Derivation: read
rustPlatform.buildRustPackagefor a trait-objects demo binary (src, lock hash, what lands in$out). - The Dev Shell: name
devShells.defaultas the editable toolchain, not the release store path. - The Flake Check: say what
nix flake checkbuilds from thechecksattribute 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
- Copy the
buildRustPackageskeleton, rename it for a trait-objects binary, and say whatcargoLock.lockFilepins. - Open a
mkShellwithrustc/cargoand state why those packages are notbuildInputsof the release derivation. - Add the package to
checksand say whatnix flake checkwill build. - 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.