Sound Source-Prune

cargo-slicer sound-prune produces a copy of your project with unused function bodies replaced by unimplemented!(), so a plain cargo build of the copy skips the work of compiling code your binary never calls — without ever producing a crashing binary.

The "without crashing" part is the whole point. Earlier reachability-by-guess approaches could mis-judge dynamic dispatch and stub a function that was reached, yielding a binary that crashed at runtime. sound-prune instead uses the linker's own reachability as ground truth.

How it works

pass 1  cargo build (debug, no inlining)   →  an oracle binary
pass 2  nm -C <binary>                      →  the exact set of functions linked
pass 3  mirror the workspace, replace every unused fn body with unimplemented!()

A function is stubbed only if its symbol is absent from the linked binary. Because the seed is link-truth, a wrong decision cannot miscompile: it can only fail the build loudly with unimplemented!() at a named line.

Usage

cargo-slicer sound-prune --output ../myproject-pruned
cd ../myproject-pruned
cargo build --release          # plain cargo; no slicer needed to build the copy

Options:

FlagMeaning
-o, --output DIROutput directory (default: <project>-pruned)
--deleteRemove private unused fns entirely (default: stub the body)
--vendorExperimental. Also cargo vendor and prune dependency sources

The default is workspace-only — it rewrites only the crates in your workspace, never your dependencies. That keeps it simple and side-effect free. --vendor reaches further but pulls in vendoring (checksums, build-dependency handling) and is opt-in.

Soundness guarantees

sound-prune never stubs:

  • functions whose symbol is in the linked binary (i.e. anything actually used);
  • trait-impl methods (impl Trait for T) — reached through dynamic/generic dispatch;
  • functions in a crate that ships a build.rs (and, with --vendor, any transitive build-dependency — build scripts run code your runtime binary never links);
  • files containing macro output the formatter cannot render.

The oracle build uses no inlining so that every used function keeps a real symbol; an optimized build would inline callees away and make them look unused.

When it helps

The speedup tracks how much un-called code sits on the compile critical path. CLI tools with a focused dependency set see the clearest win (ripgrep: ~1.2–1.3× faster clean build, identical behaviour). Generics-heavy projects see little from source-pruning alone, because most of their dead codegen is in monomorphized generic instances and trait-impl methods that source-pruning keeps for soundness — that case is what the in-tree -Zdead-fn-elimination work targets instead.