recorded decision · public · no signup

accepted2026-06-22rust-lang/rfcs

Build std always

What was chosen

  • The `mem` feature of `compiler_builtins` will be inverted to a new non-default feature named `external-mem`, which `std` will depend on.adr
  • The `build-std.crates` option will be introduced to customize which standard library crates are built.adr
  • When `build-std.when` is "always", the standard library will be unconditionally recompiled in its release profile as part of every clean build.adr
  • The standard library's dependencies will be resolved using the lockfile included in the `rust-src` component.adr
  • A new Cargo configuration option, `build-std.when = "always|never"`, will be added to unconditionally rebuild standard library dependencies.adr
  • The standard library will always be a non-incremental build, producing only `rlib` artifacts.adr
  • The host pre-built standard library will always be used for procedural macros and build scripts.adr
  • Cargo will look for standard library sources in a fixed location in the sysroot: `lib/rustlib/src`.adr
  • `rust-src` will contain the sources for the standard library crates and their vendored dependencies.adr

Constraints

  • Standard library crates and their dependencies cannot be patched or replaced by the user in Cargo manifests or config.adr

The recorded why

§ Summary

Add a new Cargo configuration option, build-std.when = "always|never", which will unconditionally rebuild standard library dependencies. The set of standard library dependencies can optionally be customised with a new build-std.crates option. It also describes how Cargo (or external tools) should build the standard library crates on stable (i.e., which flags to pass and features to enable).

This proposal limits the ways the built standard library can be customised (such as by settings in the profile) and intends that the build standard library matches the prebuilt one (if available) as closely as possible.

This RFC is is part of the build-std project goal and a series of build-std RFCs:

  1. build-std context (rfcs#3873)
  2. build-std="always" (this RFC)
    • Proposal
    • [Rationale and alternatives][rationale-and-alternatives]
    • [Unresolved questions][unresolved-questions]
    • [Future possibilities][future-possibilities]
    • [Summary of proposed changes][summary-of-changes]
  3. Explicit standard library dependencies (rfcs#3875)
  4. build-std="compatible" (RFC not opened yet)
  5. build-std="match-profile" (RFC not opened yet)

§ Motivation

This RFC builds on a large collection of prior art collated in the build-std-context RFC, and is aimed at supporting the following motivations it identifies:

  • Building the standard library without relying on unstable escape hatches
  • Building standard library crates that are not shipped for a target
  • Using the standard library with tier three targets

While the enabling and disabling of some standard library features is mentioned in this RFC (when required to support existing stable features of Cargo), the enabling and disabling of arbitrary standard library features is handled by RFC #3875.

A key goal of this proposal is that the standard library produced by build-std.when = "always" be a drop-in replacement for the pre-build standard library, so that the new surface area of the standard library exposed by build-std is minimal (while providing extension points for this to be changed in future in limited and controlled ways).

§ Proposal

This proposal section is quite broad, and a [summary of changes][summary-of-changes] is available for a very brief list of proposed changes.

Cargo configuration will contain a new table build-std under the [build] section ([?][rationale-build-std-in-config]), with a when key permitting one of two values - "never" ([?][rationale-build-std-never]) or "always", defaulting to "never":

[build]
build-std.when = "never" # or `always`

build-std can also be specified in the [target.<triple>] and [target.<cfg>] sections ([?][rationale-build-std-target-section]):

[target.aarch64-unknown-illumos]
build-std.when = "never" # or `always`

The build-std configuration locations have the following precedence ([?][rationale-build-std-precedence]):

  1. [target.<triple>]
  2. [target.<cfg>]
  3. [build]

As the Cargo configuration is local to the current user (typically in .config/cargo.toml in the project root and/or Cargo home directory), the value of build-std is not influenced by the dependencies of the current crate.

When build-std is set to "always", then the standard library will be unconditionally recompiled ([?][rationale-unconditional]) in the release profile ([?][rationale-release-profile]) defined in its workspace as part of every clean build. This is primarily useful for users of tier three targets.

As with other dependencies, the standard library's build will respect the RUSTFLAGS environment variable. RUSTFLAGS is respected irrespective of the mechanism that it is set via: RUSTFLAGS, CARGO_ENCODED_RUSTFLAGS, target.<triple>.rustflags or target.<cfg>.rustflags. The unstable profile.rustflags key is also respected, but from the same profile as the rest of the standard library build (i.e. it uses the release profile of the standard library's workspace).

[!NOTE]

Configuration of the pre-built standard library is split across bootstrap and the Cargo packages for the standard library. As much of this configuration as possible should be moved to the Cargo profile for these packages so that the artifacts produced by build-std match the pre-built standard library as much as is feasible.

Alongside build-std, a build-std.crates key will be introduced ([?][rationale-build-std-crate]), which can be used to specify which crates from the standard library should be built. Only "core", "alloc" and "std" are valid values for build-std.crates.

[build]
build-std = { when = "always", crates = "std" }

A value of "std" means that every crate in the graph has a direct dependency on std, alloc and core. Similarly, "alloc" means alloc and core, and "core" means just core.

If std is to be built and Cargo is building a test or benchmark using the default test harness then Cargo will also build the test crate.

When build-std.crates is unset, then the standard library crates built will depend on whether Standard library dependencies are implemented:

[!NOTE]

Inspired by the concept of opaque dependencies, the standard library is resolved differently to other dependencies:

  • The lockfile included in the standard library source will be used when resolving the standard library's dependencies ([?][rationale-lockfile]).

  • The dependencies of the standard library crates are entirely opaque to the user. Different semver-compatible versions of these dependencies can exist in the user's resolve. The user cannot control compilation any of the dependencies of the core, alloc or std standard library crates individually (via profile overrides, for example).

  • The release profile defined by the standard library will be used.

    • This profile will be updated to match the current compilation options used by the pre-built standard library as much as possible (e.g. using -Cembed-bitcode=yes to support LTO).
  • Standard library crates and their dependencies from build-std.crates cannot be patched/replaced by the user in the Cargo manifest or config (e.g. using source replacement, [replace] or [patch])

  • Lints in standard library crates will be built using --cap-lints allow matching other upstream dependencies.

Cargo will resolve the dependencies of opaque dependencies, such as the standard library, separately in their own workspaces. The root of such a resolve will be the crates specified in build-std.crates or, if Standard library dependencies is implemented, the unified set of packages that any crate in the dependency has a direct dependency on. A dependency on the relevant roots are added to all crates in the main resolve.

Regardless of which standard library crates are being built, Cargo will build the sysroot crate of the standard library workspace. alloc and std will be optional dependencies of the sysroot crate which will be enabled when the user has requested them. Panic runtimes are dependencies of std and will be enabled depending on the features that Cargo passes to std (see [Panic strategies][panic-strategies]).

rustc loads panic runtimes in a different way to most dependencies, and without looking in the sysroot they will fail to load correctly unless passed in with --extern. rustc will need to be patched to be able to load panic runtimes from -L dependency= paths in line with other transitive dependencies.

The standard library will always be a non-incremental build ([?][rationale-incremental]), Cargo's dep-info fingerprint tracking will not track the standard library crate sources, Cargo's .d dep-info file will not include standard library crate sources, and only a rlib produced (no dylib) ([?][rationale-no-dylib]). It will be built in the Cargo target directory of the crate or workspace like any other dependency. The rlibs of the standard library are considered intermediate artifacts.

Standard library crates are provided to the compiler using the --extern flag with the noprelude modifier ([?][rationale-noprelude-with-extern]). Standard library crates are also provided with the nounused modifier to avoid being considered an unused crate dependency ([?][rationale-nounused-with-extern]).

The host pre-built standard library will always be used for procedural macros and build scripts ([?][rationale-host-deps-cross], [?][rationale-host-deps-host]). Multi-target projects (resulting from the target field in artifact dependencies or the use of per-pkg-target fields) may result in the standard library being built multiple times - once for each target in the project.

See the following sections for rationale/alternatives:

  • [Why put build-std in the Cargo config?][rationale-build-std-in-config]
  • [Why accept never as a value for build-std?][rationale-build-std-never]
  • [Why add build-std to the [target.<triple>] and [target.<cfg>] sections?][rationale-build-std-target-section]
  • [Why does [target] take precedence over [build] for build-std?][rationale-build-std-precedence]
  • [Why does "always" rebuild unconditionally?][rationale-unconditional]
  • [Why does "always" rebuild in release profile?][rationale-release-profile]
  • [Why add build-std.crates?][rationale-build-std-crate]
  • [Why use the lockfile of the rust-src component?][rationale-lockfile]
  • [Why not build the standard library in incremental?][rationale-incremental]
  • [Why not produce a dylib for the standard library?][rationale-no-dylib]
  • [Why use noprelude with --extern?][rationale-noprelude-with-extern]
  • [Why use the pre-built standard library for procedural macros and build scripts in host mode?][rationale-host-deps-host]
  • [Why use the pre-built standard library for procedural macros and build scripts in cross-compile mode?][rationale-host-deps-cross]

See the following sections for relevant unresolved questions:

  • [What should the build-std configuration in .cargo/config.toml be named?][unresolved-config-name]
  • [What should the "always" and "never" values of build-std be named?][unresolved-config-values]
  • [What should build-std.crates be named?][unresolved-build-std-crate-name]
  • [Should the standard library inherit RUSTFLAGS?][unresolved-inherit-rustflags]

See the following sections for future possibilities:

  • [Adding a shorthand][future-shorthand]
  • [Allow reusing sysroot artifacts if available][future-reuse-sysroot]

Standard library crate stability

An optional standard_library_support field ([?][rationale-why-standard-library-support]) is added to the target specification ([?][rationale-target-spec-purpose]), replacing the existing metadata.std field. standard_library_support has two fields:

  • supported, which can be set to either "core", "core, alloc", or "core, alloc, std"
  • default, which can be set to either "core", "core, alloc", or "core, alloc, std"
    • default cannot be set to a value which is "less than" that of supported (i.e. "core, alloc" when supported was only set to "core")

The supported field determines which standard library crates Cargo will permit to be built for this target on a stable toolchain. On a nightly toolchain, Cargo will build whichever standard library crates are requested by the user.

The default field determines which crate will be built by Cargo if build-std.when = "always" and build-std.crates is not set. Users can specify build-std.crates to build more crates than included in the default, as long as those crates are included in supported.

The correct value for standard_library_support is independent of the tier of the target and depends on the set of crates that are intended to work for a given target, according to its maintainers.

If standard_library_support is unset for a target, then Cargo will not permit any standard library crates to be built for the target on a stable toolchain. It will be required to use a nightly toolchain to use build-std with that target.

Cargo's build-std.crates field will default to the value of the standard_library_support.default field (std for "core, alloc, std", alloc for "core, alloc", and core for "core"). This does not prevent users from building more crates than the default, it is only intended to be a sensible default for the target that is probably what the user expects.

The target-standard-library-support option will be supported by rustc's --print flag and will be used by Cargo to query this value for a given target:

$ rustc --print target-standard-library-support --target armv7a-none-eabi
default: core
supported: core, alloc
$ rustc --print target-standard-library-support --target aarch64-unknown-linux-gnu
default: std
supported: core, alloc, std

Following compiler-team#860, target-standard-library-support can also be output in JSON:

$ rustc --print target-standard-library-support:json --target armv7a-none-eabi
{ "default": "core", "supported": ["core", "alloc"] }
$ rustc --print target-standard-library-support:json --target aarch64-unknown-linux-gnu
{ "default": "core", "supported": ["core", "alloc", "std"] }

See the following sections for rationale/alternatives:

  • [Why introduce standard_library_support?][rationale-why-standard-library-support]
  • [Should target specifications own knowledge of which standard library crates are supported?][rationale-target-spec-purpose]

Interactions with #![no_std]

Behaviour of crates using #![no_std] will not change whether or not std is rebuilt and passed via --extern to rustc, and #![no_std] will still be required in order for rustc to not attempt to load std and add it to the extern prelude. Standard library dependencies describes a future possibility for how the no_std mechanism could be replaced.

See the following sections for future possibilities:

restricted_std

The existing restricted_std mechanism will be removed from std's build.rs.

See the following sections for rationale/alternatives:

  • [Why remove restricted_std?][rationale-remove-restricted-std]

Custom targets

Cargo will detect when the standard library is to be built for a custom target and will emit an error ([?][rationale-disallow-custom-targets]).

[!NOTE]

Cargo could detect use of a custom target either by comparing it with the list of built-in targets that rustc reports knowing about (via --print target-list) or by checking if a file exists at the path matching the provided target name.

This does not require any changes to rustc. If it is invoked to build the standard library then it will continue to do so, as is possible today, it is only the build-std functionality in Cargo that will not support custom targets initially.

Furthermore, custom targets will be destabilised in rustc (as in [rust#71009]). This will not be a significant breaking change as custom targets cannot effectively be used currently without nightly (needing build-std to have core).

Custom targets can still be used with build-std on nightly toolchains provided that -Zunstable-options is provided to Cargo.

See the following sections for rationale/alternatives:

  • [Why disallow custom targets?][rationale-disallow-custom-targets]

See the following sections for future possibilities:

  • [Allow custom targets with build-std][future-custom-targets]

Preventing implicit sysroot dependencies

Cargo will pass a new flag to rustc which will prevent rustc from loading top-level dependencies from the sysroot ([?][rationale-root-sysroot-deps]).

[!NOTE]

rustc could add a --no-implicit-sysroot-deps flag with this behaviour. For example, writing extern crate foo in a crate will not load foo.rlib from the sysroot if it is present, but if an --extern noprelude:bar.rlib is provided which depends on a crate foo, rustc will look in -L dependency=... paths and the sysroot for it.

See the following sections for rationale/alternatives:

  • [Why prevent rustc from loading root dependencies from the sysroot?][rationale-root-sysroot-deps]

Vendored rust-src

When it is necessary to build the standard library, Cargo will look for sources in a fixed location in the sysr

Excerpt — the full document is at the cited source: text/3874-build-std-always.md