recorded decision · public · no signup
error: must have a non-optional dependency on core
What was chosen
- Standard library dependencies will be present in the registry index to allow them as dependencies of other crates.adr ↗
- The `builtin = true` key is a new dependency source, usable only for `core`, `alloc`, or `std` on stable.adr ↗
- Explicit dependencies are passed to rustc without the `noprelude` modifier for consistent user experience.adr ↗
- Crates without explicit standard library dependencies retain an implicit dependency on the target's default set.adr ↗
- Explicit standard library dependencies default to private, consistent with other manifest dependencies.adr ↗
- Users can now optionally declare explicit dependencies on standard library crates in their `Cargo.toml` files.adr ↗
- Standard library dependencies can be marked `optional` and enabled conditionally by a feature in the crate.adr ↗
- Implicit standard library dependencies default to public to avoid breaking existing crates re-exporting from `std`.adr ↗
Constraints
- If an optional standard library dependency exists, a non-optional `core` dependency must also be present.adr ↗
Consequences
- Any explicit standard library dependency disables the implicit dependencies for that target.adr ↗
The recorded why
- Feature Name:
build-std-explicit-dependencies - Start Date: 2025-06-05
- RFC PR: rust-lang/rfcs#3875
- Tracking Issue: rust-lang/cargo#16960
§ Summary
Allow users to add explicit dependencies on standard library crates in the
Cargo.toml. This enables Cargo to determine which standard library crates are
required by the crate graph without build-std.crates being set and for
different crates to require different standard library crates.
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"(rfcs#3874)- Explicit standard library dependencies (this RFC)
- Proposal
- [Rationale and alternatives][rationale-and-alternatives]
- [Unresolved questions][unresolved-questions]
- [Future possibilities][future-possibilities]
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. It does not directly address the
main rfcs#3873-motivation it identifies but supports later proposals.
The main motivation for this proposal is to support future extensions to build-std which allow public/private standard library dependencies or enabling features of the standard library. Allowing the standard library to behave similarly to other dependencies also reduces user friction and can improve build times.
§ Proposal
Users can now optionally declare explicit dependencies on the standard library
in their Cargo.toml files ([?][rationale-why-explicit-deps]):
[package]
name = "hello_world"
version = "0.1.0"
edition = "2024"
[dependencies]
std = { builtin = true }
builtin is a new source of dependency, like registry dependencies (with the
version key and optionally the registry key), path dependencies or git
dependencies. builtin can only be set to true and cannot be combined with
any other dependency source for a given dependency
([?][rationale-builtin-other-sources]).
builtin can only be used with crates named core, alloc or std
([?][rationale-no-builtin-other-crates]) on stable. This set could be expanded
with new crates in future.
Use with any other crate name is gated on a perma-unstable cargo-feature
([?][rationale-unstable-builtin-crates]). If a builtin dependency on a unstable
crate name exists but is not used due to cfgs, then Cargo will still require the
Cargo feature.
[!NOTE]
Explicit dependencies are passed to rustc without the
nopreludemodifier ([?][rationale-explicit-noprelude]) (nopreluderefers to the compiler's notion of the "extern prelude", not the prelude in the the user-facing sense ofstd::prelude::*).When adding an explicit dependency, users may need to adjust their code (removing extraneous
extern cratestatements or root-relative paths, like::std- this will likely only be the case on the 2015 edition).
Crates without an explicit dependency on the standard library now have a
implicit dependency ([?][rationale-no-migration]) on that target's default set
of standard library crates (see
build-std-always). Any explicit
standard library dependency present in any dependency table applicable to the
current target will disable the implicit dependencies (e.g. an explicit
builtin or path dependency from std will disable the implicit
dependencies).
[!NOTE]
Implicit dependencies are passed to rustc with the
nopreludemodifier to ensure backwards compatibility as inbuild-std=always.
When a std dependency is present an additional implicit dependency on the
test crate is added for crates that are being tested with the default test
harness. The test crate's name, but not its interface, will be stabilised so
Cargo can refer to it.
crates.io will accept crates published which have builtin dependencies.
Standard library dependencies can be marked as optional and be enabled
conditionally by a feature in the crate:
[package]
name = "hello_world"
version = "0.1.0"
edition = "2024"
[dependencies]
std = { builtin = true, optional = true }
core = { builtin = true }
[features]
default = ["std"]
std = ["dep:std"]
If there is an optional dependency on the standard library then Cargo will
validate that there is at least one non-optional dependency on the standard
library (e.g. an optional std and non-optional core or alloc, or an
optional alloc and non-optional core). core cannot be optional. For
example, the following example will error as it could result in a build without
core (if the std feature were disabled):
[package]
name = "hello_world"
version = "0.1.0"
edition = "2024"
[dependencies]
std = { builtin = true, optional = true }
# error: must have a non-optional dependency on core
[features]
default = ["std"]
std = ["dep:std"]
However, in this example, a build for the x86-64-pc-windows-gnu target would
have an explicit dependency on alloc (and indirectly on core), while a build
for any other target would have implicit dependencies on std, alloc and
core:
[package]
name = "hello_world"
version = "0.1.0"
edition = "2024"
[dependencies]
# implicit deps on `core`, `alloc` and `std` unless target='x86_64-pc-windows-gnu'
[target.x86_64-pc-windows-gnu.dependencies]
alloc.builtin = true
Dependencies with builtin = true cannot be renamed with the package key
([?][rationale-package-key]). It is not possible to perform source replacement
on the builtin source using the [source] Cargo config table
([?][rationale-source-replacement]), and nor is it possible to override
builtin dependencies with the [replace] sections or paths overrides
([?][rationale-overriding-builtins]), though patching is permitted
under a perma-unstable feature flag ([?][rationale-patching]).
Dependencies with builtin = true can be specified as platform-specific
dependencies:
[target.'cfg(unix)'.dependencies]
std = { builtin = true}
Implicit and explicit standard library dependencies are added to Cargo.lock
files ([?][rationale-cargo-lock]).
[!NOTE]
A new version of the
Cargo.lockfile will be introduced to add support for packages with abuiltinsource:[[package]] name = "std" version = "0.0.0" source = "builtin"The package version of
std,allocandcorewill be fixed at0.0.0. The optional lockfile fieldsdependenciesandchecksumwill not be present forbuiltindependencies.
A perma-unstable Cargo feature for disabling all standard library dependencies will
be added to allow the core crate to be defined.
When a crate has no builtin dependency on std (or an optional builtin
dependency on std), then Cargo will pass -Zcrate-attr=no_std to rustc (or
some equivalent; [?][rationale-no_std]).
Builtin dependencies defined in workspace.dependencies are inherited by
members of the workspace in the same way as any other dependency and then the
same behaviour and constraints apply.
See the following sections for rationale/alternatives:
- [Why explicitly declare dependencies on the standard library in
Cargo.toml?][rationale-why-explicit-deps] - [Why disallow builtin dependencies to be combined with other sources?][rationale-builtin-other-sources]
- [Why disallow builtin dependencies on other crates?][rationale-no-builtin-other-crates]
- [Why unstably allow all names for
builtincrates?][rationale-unstable-builtin-crates] - [Why not use
nopreludefor explicitbuiltindependencies?][rationale-explicit-noprelude] - [Why not require builtin dependencies instead of supporting implicit ones?][rationale-no-migration]
- [Why disallow renaming standard library dependencies?][rationale-package-key]
- [Why disallow source replacement on
builtinpackages?][rationale-source-replacement] - [Why add standard library dependencies to Cargo.lock?][rationale-cargo-lock]
- [Why pass
-Zcrate-attr=no_stdto rustc?][rationale-no_std]
See the following sections for relevant unresolved questions:
- [What syntax is used to identify dependencies on the standard library in
Cargo.toml?][unresolved-dep-syntax] - [What is the format for builtin dependencies in
Cargo.lock?][unresolved-lockfile]
See the following sections for future possibilities:
- [Allow unstable crate names to be referenced behind cfgs without requiring nightly][future-cfg-unstable-crate-name]
- [Allow
builtinsource replacement][future-source-replacement] - [Remove
rustc_dep_of_std][future-rustc_dep_of_std]
Non-builtin standard library dependencies
Cargo already supports path and git dependencies for crates named core,
alloc and std which continue to be supported and work:
[package]
name = "hello_world"
version = "0.1.0"
edition = "2024"
[dependencies]
std = { path = "../my_std" } # already supported by Cargo
A core/alloc/std dependency with a path/git source can be combined
with builtin dependencies:
[package]
name = "hello_world"
version = "0.1.0"
edition = "2024"
[dependencies]
std = { path = "../my_std" }
core = { builtin = true }
Crates with these dependency sources will remain unable to be published to crates.io.
Building on both of the above, a core/alloc/std dependency can have
path/git source at the same time as a builtin source. This allows the
crate to remain publishable while the path/git source is only used locally:
[package]
name = "hello_world"
version = "0.1.0"
edition = "2024"
[dependencies]
std = { builtin = true, path = "../my_std" } # published as `builtin` dep, `path` used locally
This mirrors the existing behaviour for path/git sources when combined with
registry sources.
Patches
Under a perma-unstable feature it is permitted to patch standard library
dependencies with path and git sources (or any other source)
([?][rationale-patching]):
[package]
name = "hello_world"
version = "0.1.0"
edition = "2024"
[dependencies]
std = { builtin = true }
[patch.builtin] # permitted on nightly
std = { path = "../libstd" }
As with dependencies, crates with path/git patches for core, alloc or
std are not accepted by crates.io.
See the following sections for rationale/alternatives:
- [Why unstably permit patching of standard library dependencies?][rationale-patching]
See the following sections for relevant unresolved questions:
- [What syntax is used to patch dependencies on the standard library in
Cargo.toml?][unresolved-patch-syntax]
Features
On a stable toolchain, it is not permitted to enable or disable features of explicit standard library dependencies ([?][rationale-features]), as in the below example:
[package]
name = "hello_world"
version = "0.1.0"
edition = "2024"
[dependencies]
std = { builtin = true, features = [ "foo" ] } # not permitted
# ..or..
std = { builtin = true, default-features = false } # not permitted
See the following sections for rationale/alternatives:
- [Why limit enabling standard library features to an unstable feature?][rationale-features]
See the following sections for future possibilities:
- [Allow enabling/disabling features with build-std][future-features]
Public and private dependencies
Implicit dependencies on the standard library default to being public dependencies ([?][rationale-default-implicit-public]).
[package]
name = "hello_world"
version = "0.1.0"
edition = "2024"
[dependencies]
..is equivalent to the following explicit dependency on std:
[package]
name = "hello_world"
version = "0.1.0"
edition = "2024"
[dependencies]
std = { builtin = true, public = true }
Explicit dependencies on the standard library default to being private
dependencies ([?][rationale-default-implicit-public]). cargo add will add
public = true by default for builtin dependencies (see Cargo
subcommands).
See the following sections for relevant unresolved questions:
- [Should implicit standard library dependencies default to public?][unresolved-std-default-implicit-public]
- [Should explicit standard library dependencies default to private?][unresolved-std-default-explicit-private]
See the following sections for rationale/alternatives:
- [Why default to public for implicit standard library dependencies?][rationale-default-implicit-public]
- [Why default to private for explicit standard library dependencies?][rationale-default-explicit-private]
dev-dependencies and build-dependencies
Explicit dependencies on the standard library can be specified in
dev-dependencies in the same way as regular dependencies. Any explicit
builtin dependency present in dev-dependencies table will disable the
implicit dependencies. It is possible for dev-dependencies to have additional
builtin dependencies that the dependencies section does not have (e.g.
requiring std when the regular dependencies only require core).
Build scripts and proc macros continue to use the pre-built standard library as
in build-std=always, and so explicit dependencies on the
standard library are not supported in build-dependencies.
See the following sections for relevant unresolved questions:
- [Should we support
build-dependencies?][unresolved-build-deps]
Registries
Standard library dependencies will be present in the registry index such that standard library dependencies can be dependencies of other crates, but not as top-level crates in the registry ([?][rationale-cargo-index]).
A builtin_deps key is added to the [index's JSON schema][cargo-json-schema]
([?][rationale-cargo-builtindeps]). builtin_deps is similar to the existing
deps key and contains a list of JSON objects, each representing a dependency
that is "builtin" to the Rust toolchain and cannot otherwise be found in the
registry. The "publish" endpoint of the Registry
Web API will similarly be updated to support builtin_deps.
[!NOTE]
It is expected that the keys of these objects will be:
name
- String containing name of the
builtinpackage. Can shadow the names of other packages in the registry (except those packages in thedepskey of the current package) ([?][rationale-cargo-index-shadowing])features:
- An array of strings containing enabled features in order to support changing the standard library features on nightly. Optional, empty by default.
optional,default_features,target,kind:
- These keys have the same definition as in the
depskeyThe keys
req,registryandpackagefromdepsare not required per the limitations on builtin dependencies.The
builtin_depskey is optional and if not present its default value will be the implicit builtin dependencies:"builtin_deps" : [ { "name": "std", "features": [], "optional": false, "default_features": true, "target": null, "kind": "normal", }, { "name": "alloc", ... # as above }, { "name": "core", ... # as above } ]When producing a registry index entry for a package Cargo will strip any
builtindependencies that match the implicit state. This allows the implicit state to change in the future if needed and prevents publishing a package with a new Cargo from raising your MSRV. Similarly, the publishedCargo.tomlwill not explicitly declare any dependencies that match the implicit state.builtindependencies that do match the implicit state could be made explicit in a future edition.
See the following sections for rationale/alternatives:
- [Why add standard library crates to Cargo's index?][rationale-cargo-index]
- [Why add a new key to Cargo's registry index JSON schema?][rationale-cargo-builtindeps]
- [Why can
builtin_depsshadow other packages in the registry?][rationale-cargo-index-shadowing]
See the following sections for relevant unresolved questions:
- [Should parsed manifests be used instead of the registry index?][unresolved-registry-changes]
Cargo subcommands
Any Cargo command which accepts a package spec with -p will now additionally
recognise core, alloc and std and none of their dependencies. Many of
Cargo's subcommands will need modification to support build-std:
[cargo add][cargo-add]'s heuristics will include adding std, alloc or
core as builtin dependencies if these crate names are provided. cargo add
will additionally have a --builtin flag to allow for adding crates with a
builtin source explicitly:
[package]
name = "hello_world"
version = "0.1.0"
edition = "2024"
[dependencies]
std = { builtin = true } # <-- this would be added
If attempting to add a crate name outside of core, alloc or std this will
fail unless the required cargo-feature is added to allow other builtin crate
names as described in [the rationale][rationale-unstable-builtin-crates].
If attempting to add a builtin crate with features then this will fail unless
the required cargo-feature is enabled as described in [Features][featur
Excerpt — the full document is at the cited source: text/3875-build-std-explicit-dependencies.md ↗