recorded decision · public · no signup
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
- Feature Name:
build-std-always - Start Date: 2025-06-05
- RFC PR: rust-lang/rfcs#3874
- Rust Issue: rust-lang/rust#155363
§ 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:
- build-std context (rfcs#3873)
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]
- Explicit standard library dependencies (rfcs#3875)
build-std="compatible"(RFC not opened yet)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]):
[target.<triple>][target.<cfg>][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:
-
If Standard library dependencies is not implemented, then the crates intended to be supported by the target inform which crates are built (see later Standard library crate stability section).
-
If Standard library dependencies is implemented, then the implicit or explicit
builtindependencies of the crate graph inform which crates are built .
[!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,allocorstdstandard 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=yesto support LTO).Standard library crates and their dependencies from
build-std.cratescannot 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 allowmatching 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.cratesor, 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
sysrootcrate of the standard library workspace.allocandstdwill be optional dependencies of thesysrootcrate which will be enabled when the user has requested them. Panic runtimes are dependencies ofstdand will be enabled depending on the features that Cargo passes tostd(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
.ddep-info file will not include standard library crate sources, and only arlibproduced (nodylib) ([?][rationale-no-dylib]). It will be built in the Cargotargetdirectory of the crate or workspace like any other dependency. Therlibs of the standard library are considered intermediate artifacts.Standard library crates are provided to the compiler using the
--externflag with thenopreludemodifier ([?][rationale-noprelude-with-extern]). Standard library crates are also provided with thenounusedmodifier 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-stdin the Cargo config?][rationale-build-std-in-config] - [Why accept
neveras a value forbuild-std?][rationale-build-std-never] - [Why add
build-stdto the[target.<triple>]and[target.<cfg>]sections?][rationale-build-std-target-section] - [Why does
[target]take precedence over[build]forbuild-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-srccomponent?][rationale-lockfile] - [Why not build the standard library in incremental?][rationale-incremental]
- [Why not produce a
dylibfor the standard library?][rationale-no-dylib] - [Why use
nopreludewith--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-stdconfiguration in.cargo/config.tomlbe named?][unresolved-config-name] - [What should the "always" and "never" values of
build-stdbe named?][unresolved-config-values] - [What should
build-std.cratesbe 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"defaultcannot be set to a value which is "less than" that ofsupported(i.e. "core, alloc" whensupportedwas 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-depsflag with this behaviour. For example, writingextern crate fooin a crate will not loadfoo.rlibfrom the sysroot if it is present, but if an--extern noprelude:bar.rlibis provided which depends on a cratefoo, 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 ↗