The target/ directory in a Rust project has a documentation problem. The Cargo reference describes it as a build cache, an internal implementation detail, not a stable interface. But the practical situation is that a large portion of the Rust tooling ecosystem, CI scripts, packaging tools, coverage runners, build caching systems, and test harnesses, treats it as one. The Cargo team is now calling for testing of -Zbuild-dir-new-layout on nightly builds from 2026-03-10 onward, and the result will tell us how much of the ecosystem has been quietly living on borrowed time.
What the Layout Actually Looks Like
The current target/debug/ directory holds two fundamentally different categories of files with no structural boundary between them. Final artifacts, the binary your Cargo.toml describes, the .rlib your library consumers link against, land at the top level of target/debug/. Intermediate build state, the .fingerprint/ directory for Cargo’s change detection, the deps/ folder holding thousands of .rlib and .rmeta files for dependencies, the incremental/ directory where rustc stores LLVM IR and salsa data for incremental recompilation, and the build/ subdirectory for build script outputs, sits alongside them with no visual or structural distinction.
This happened by accretion. Early Cargo (circa 2014) put everything in one place because it was simple. Incremental compilation arrived in Rust 1.24 (2018) and added incremental/. Build scripts needed an OUT_DIR environment variable pointing somewhere stable, so build/ appeared. Each feature appended to the structure without stepping back to reconsider the whole. The consequence is that target/debug/ carries tens of gigabytes of state that most people neither want to archive nor understand, mixed with the small set of files they actually care about.
The OUT_DIR Traversal Problem
Of the failure modes the call for testing identifies, the build script case is technically the most interesting. When Cargo runs a build script, it sets OUT_DIR to a directory inside build/ where the script can write generated files and outputs. The structure looks roughly like this:
target/debug/build/
└── mylib-3a7b2c1d/
├── build-script-build
└── out/ ← OUT_DIR points here
A pattern that appears in real-world build scripts is traversing upward from OUT_DIR by a known number of levels, then back down into a sibling crate’s output. Something like this:
// In a build script: climb from OUT_DIR to find a sibling crate's output
let out_dir = std::env::var("OUT_DIR").unwrap();
let target_dir = std::path::Path::new(&out_dir)
.ancestors()
.nth(4) // climb: out/ → mylib-3a7b2c1d/ → build/ → debug/ → target/
.unwrap();
let sibling_output = target_dir.join("debug/sibling-bin");
This pattern works today because the depth of OUT_DIR within the target directory is constant and predictable. Under the new layout, build/ and everything inside it moves to a separate build-dir location, while final outputs remain in target/. The traversal from OUT_DIR now reaches a completely different directory tree than the one containing final binaries. Scripts that count directory levels silently find the wrong place.
This is not a theoretical problem. The call for testing specifically mentions build scripts that try to locate target/ by climbing from OUT_DIR. The fix is to use the CARGO_TARGET_DIR or CARGO_MANIFEST_DIR environment variables where appropriate, but that requires knowing the problem exists before the layout change becomes the default.
The Stable API That Shipped Six Years Ago
For integration tests specifically, Cargo has provided a stable path-resolution API since Rust 1.43.0 (April 2020). When Cargo runs tests in the tests/ directory, it sets CARGO_BIN_EXE_<name> for each binary in the workspace, where <name> matches the [[bin]] name in Cargo.toml. The value is the absolute path to that binary, set correctly regardless of layout, profile, target triple, or CARGO_TARGET_DIR override:
#[test]
fn cli_version_output() {
let bin = env!"CARGO_BIN_EXE_mycli");
let out = std::process::Command::new(bin)
.arg("--version")
.output()
.unwrap();
assert!(out.status.success());
}
The env! macro captures the path at compile time. For cases where the path is needed at runtime, std::env::var_os("CARGO_BIN_EXE_myapp") works from Cargo 1.94 onward. The call for testing recommends this form for newer projects, with the path-construction approach as a fallback for older Cargo versions.
The uncomfortable reality is that the ecosystem did not broadly migrate to this API because the fragile alternative kept working. A test that walks ../target/debug/mybinary from CARGO_MANIFEST_DIR produces correct results in the standard layout, so there is no feedback loop pushing developers toward the stable form. Six years of compatibility created a false sense that the unofficial approach was reliable. The layout change removes that false sense.
What a Crater Run Cannot See
Before calling for community testing, the Cargo team ran a crater analysis. Crater builds and tests every crate published to crates.io under a proposed change, covering tens of thousands of packages, and reports regressions. A clean crater run means published test suites do not regress. That is genuinely useful.
What crater cannot reach is everything that lives outside Cargo.toml test configurations. Docker multi-stage builds that do COPY target/release/myapp /usr/local/bin/ still work because final output paths are preserved, but Dockerfiles that copy target/debug/deps/ for caching purposes, or that infer binary names by listing executables in target/debug/, are invisible to crater. CI pipelines that construct artifact paths with shell variables like BIN=target/debug/${CRATE_NAME} and test for binary existence before deployment are not crates on crates.io. Makefiles, justfiles, and xtask workspace patterns that hardcode directory segments into path construction do not appear in crater output. Packaging tools like cargo-deb or cargo-bundle, which locate executables by scanning target/release/ with heuristics, may or may not be tested by their published test suites in ways that cover the directory traversal logic.
The community testing phase is specifically designed to surface these cases. Each report to the tracking issue becomes a reference point for anyone else running the same tool and encountering the same failure. The Cargo team wants to know whether tools need updating to support both layouts simultaneously, because the transition period, where old Cargo versions and new layout semantics may coexist in a toolchain, requires some tools to handle both cases gracefully.
The Diagnostic Process
Testing is straightforward on a nightly toolchain from 2026-03-10 or later:
# Test with the new layout
cargo +nightly test -Zbuild-dir-new-layout
cargo +nightly build --release -Zbuild-dir-new-layout
# Isolate whether the build-dir routing is the source of problems
CARGO_BUILD_BUILD_DIR=build cargo +nightly test
The second command, using CARGO_BUILD_BUILD_DIR without the layout flag, tests whether the failure is in build-dir separation itself (available since Cargo 1.91) versus in the new internal structure of the build directory. If failures appear only with -Zbuild-dir-new-layout, the problem is specific to path assumptions within the layout. If failures appear with just CARGO_BUILD_BUILD_DIR=build, the issue predates the layout change. That distinction narrows the fix considerably.
Tools worth specifically testing include cargo-nextest, coverage runners like cargo-tarpaulin and cargo-llvm-cov (both of which have historically queried target/debug/deps/ directly for instrumented binaries), any xtask workspace tooling, and any CI script that constructs artifact paths with string operations rather than relying on CARGO_BIN_EXE_*.
Why Artifact Dependencies Make This Necessary
The build-dir and layout v2 work is part of a longer trajectory. Artifact dependencies, originally specified in RFC 3028, allow a crate to declare a dependency on the compiled binary output of another crate, enabling build systems where one crate produces a binary that another crate’s build script embeds or executes. This is useful for firmware builds, plugin systems, and anything where the build graph includes compiled binary artifacts as inputs to later compilation steps.
Artifact dependencies require a stable, reliable way to locate binary outputs across all possible build configurations, profiles, target triples, and directory overrides. The current situation, where a binary’s location can be derived from a mix of undocumented directory structure, hash suffixes, and profile names, makes that unreliable. A clean separation between final outputs in target/ and intermediate state in build-dir provides the structural foundation for a location API that stays correct regardless of how the internal layout evolves.
In other words, the layout v2 work is both cleanup and groundwork. It addresses six years of accumulated directory structure problems and clears the path for features that require a principled artifact location model. The call for testing is the last major step before the new layout can become the default, and the window to find breakage before that happens is now.