Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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:

  1. build-std context (rfcs#3873)
  2. build-std="always" (rfcs#3874)
  3. Explicit standard library dependencies (this RFC)
  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. 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 (?):

[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 (?).

builtin can only be used with crates named core, alloc or std (?) 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 (?). 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 noprelude modifier (?) (noprelude refers to the compiler’s notion of the “extern prelude”, not the prelude in the the user-facing sense of std::prelude::*).

When adding an explicit dependency, users may need to adjust their code (removing extraneous extern crate statements 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 (?) 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 noprelude modifier to ensure backwards compatibility as in build-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 (?). It is not possible to perform source replacement on the builtin source using the [source] Cargo config table (?), and nor is it possible to override builtin dependencies with the [replace] sections or paths overrides (?), though patching is permitted under a perma-unstable feature flag (?).

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 (?).

Note

A new version of the Cargo.lock file will be introduced to add support for packages with a builtin source:

[[package]]
name = "std"
version = "0.0.0"
source = "builtin"

The package version of std, alloc and core will be fixed at 0.0.0. The optional lockfile fields dependencies and checksum will not be present for builtin dependencies.

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; ?).

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:

See the following sections for relevant unresolved questions:

See the following sections for future possibilities:

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) (?):

[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:

See the following sections for relevant unresolved questions:

Features

On a stable toolchain, it is not permitted to enable or disable features of explicit standard library dependencies (?), 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:

See the following sections for future possibilities:

Public and private dependencies

Implicit dependencies on the standard library default to being public dependencies (?).

[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 (?). cargo add will add public = true by default for builtin dependencies (see Cargo subcommands).

See the following sections for relevant unresolved questions:

See the following sections for rationale/alternatives:

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:

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 (?).

A builtin_deps key is added to the index’s JSON schema (?). 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 builtin package. Can shadow the names of other packages in the registry (except those packages in the deps key of the current package) (?)
  • 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 deps key

The keys req, registry and package from deps are not required per the limitations on builtin dependencies.

The builtin_deps key 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 builtin dependencies 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 published Cargo.toml will not explicitly declare any dependencies that match the implicit state. builtin dependencies that do match the implicit state could be made explicit in a future edition.

See the following sections for rationale/alternatives:

See the following sections for relevant unresolved questions:

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’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.

If attempting to add a builtin crate with features then this will fail unless the required cargo-feature is enabled as described in Features.

Once public and private dependencies are stabilised (rust#44663), cargo add will add public = true by default for the standard library dependencies added:

[package]
name = "hello_world"
version = "0.1.0"
edition = "2024"

[dependencies]
std = { builtin = true, public = true } # <-- this would be added

If adding std or alloc with --optional, then a non-optional core would also be added:

[package]
name = "hello_world"
version = "0.1.0"
edition = "2024"

[dependencies]
std = { builtin = true, optional = true } # <-- this would be added
core = { builtin = true } # <-- this would also be added

cargo info will learn how to print information for the built-in std, alloc and core dependencies:

$ cargo info std
std
rust standard library
license: Apache 2.0 + MIT
rust-version: 1.86.0
documentation: https://doc.rust-lang.org/1.86.0/std/index.html
$ cargo info alloc
alloc
rust standard library
license: Apache 2.0 + MIT
rust-version: 1.86.0
documentation: https://doc.rust-lang.org/1.86.0/alloc/index.html
$ cargo info core
core
rust standard library
license: Apache 2.0 + MIT
rust-version: 1.86.0
documentation: https://doc.rust-lang.org/1.86.0/core/index.html

cargo metadata will emit std, alloc and core dependencies to the metadata emitted by cargo metadata (when those crates differ from the implicit state). source would be set to builtin and the remaining fields would be set like any other dependency. Per the cargo metadata Compatibility documentation, adding a new source kind is not considered an incompatible change. See also unresolved question Should cargo metadata include the standard library’s dependencies?.

Note

cargo metadata output could look as follows:

{
  "packages": [
    {
      /* ... */
      "dependencies": [
        {
          "name": "std",
          "source": "builtin",
          "req": "*",
          "kind": null,
          "rename": null,
          "optional": false,
          "uses_default_features": true,
          "features": ["compiler-builtins-mem"],
          "target": null,
          "public": true
        }
      ],
      /* ... */
    }
  ]
}

cargo pkgid when passed -p core would print builtin://.#core as the source, likewise with alloc and std. This format complies with Cargo’s spec for Package IDs. See also unresolved question What should the exact format of the pkgid spec for builtin dependencies be?.

cargo remove will remove core, alloc or std explicitly from the manifest if invoked with those crate names (using the same heuristics as those described above for cargo add):

[package]
name = "hello_world"
version = "0.1.0"
edition = "2024"

[dependencies]
std = { builtin = true } # <-- this would be removed

cargo tree will show std, alloc and core at appropriate places in the tree of dependencies. As opaque dependencies, none of the other dependencies of std, alloc or core will be shown. Neither std, alloc or core will have a version number.

Note

cargo tree output could look as follows:

$ cargo tree
myproject v0.1.0 (/myproject)
├── rand v0.7.3
│   ├── getrandom v0.1.14
│   │   ├── cfg-if v0.1.10
│   │   │   └── core (built-in)
│   │   ├── libc v0.2.68
│   │   │   └── core (built-in)
│   │   └── core (built-in)
│   ├── libc v0.2.68 (*)
│   │   └── core (built-in)
│   ├── rand_chacha v0.2.2
│   │   ├── ppv-lite86 v0.2.6
│   │   │   └── core (built-in)
│   │   ├── rand_core v0.5.1
│   │   │   ├── getrandom v0.1.14 (*)
│   │   │   └── core (built-in)
│   │   └── std (built-in)
│   │       └── alloc (built-in)
│   │           └── core (built-in)
│   ├── rand_core v0.5.1 (*)
│   └── std (built-in) (*)
└── std (built-in) (*)

This part of the RFC has no implications for the following Cargo subcommands:

Rationale and alternatives

This section aims to justify all of the decisions made in the proposed design from Proposal and discuss why alternatives were not chosen.

Why explicitly declare dependencies on the standard library in Cargo.toml?

If there are no explicit dependencies on standard library crates, Cargo would need to be able to determine which standard library crates to build when this is required:

  • Cargo could unconditionally build std, alloc and core. Not only would this be unnecessary and wasteful for no_std crates in the embedded ecosystem, but sometimes a target may not support building std at all and this would cause the build to fail.

  • rustc could support a --print value that would print whether the crate declares itself as #![no_std] crate, and based on this, Cargo could build std or only core. This would require asking rustc to parse crates’ sources while resolving dependencies, slowing build times. Alternatively, Cargo can already read Rust source to detect frontmatter (for cargo script) so it could additionally look for #![no_std] itself. Regardless of how it determines a crate is no-std, Cargo would also need to know whether to build alloc too, which checking for #![no_std] does not help with. Cargo could go further and ask rustc whether a crate (or its dependencies) used alloc, but this seems needlessly complicated.

  • Cargo could allow the user to specify which crates are required to be built, such as with the existing options to the -Zbuild-std= flag. build-std=always proposes a build-std.crates flag to enable explicit dependencies to be a separate part of this RFC.

Furthermore, supporting explicit dependencies on standard library crates enables use of other Cargo features that apply to dependencies in a natural and intuitive way. If there were not explicit standard library dependencies and enabling features on the std crate was desirable, then a mechanism other than the standard syntax for this would be necessary, such as a flag (e.g. -Zbuild-std-features) or option in Cargo’s configuration. This also applies to optional dependencies, public/private features, etc.

Users already use Cargo features to toggle #![no_std] in crates which support building without the standard library. When dependencies on the standard library are exposed in Cargo.toml then they can be made optional and enabled by the existing Cargo features that crates already have.

Proposal

Why disallow builtin dependencies to be combined with other sources?

If using path/git sources with builtin dependencies worked in the same way as using path/git sources with version sources, then: crates with path/git standard library dependencies could be pushed to crates.io.

This is not desirable as it is unclear that supporting path/git sources which shadow standard library crates was a deliberate choice and so enabling that pattern to be used more widely when not necessary is needlessly permissive.

In addition, when combined with a git/path source, the version constraint also applies to package from the git/path source. If version were used alongside builtin, then this behaviour would be a poor fit as..

  • ..the std, alloc and core crates all currently have a version of 0.0.0

  • ..choosing different version requirements for different builtin crates is confusing when a single version of these crates is provided by the toolchain

Hypothetically, choosing a different version for builtin crates could be a way of supporting per-target/per-profile MSRVs, but this has limited utility.

Proposal

Why disallow builtin dependencies on other crates?

builtin dependencies could be accepted on two other crates - dependencies of the standard library, like compiler_builtins, or other crates in the sysroot added manually by users, however:

  • The standard library’s dependencies are not part of the stable interface of the standard library and it is not desirable that users can observe their existence or depend on them directly

  • Other crates in the sysroot added by users are not something that can reasonably be supported by build-std and these crates should become regular dependencies

Proposal

Why unstably allow all names for builtin crates?

For any crate shipped with the standard library in the sysroot, the user can already write an extern crate declaration to use it. Most are marked unstable either explicitly or implicitly with the use of -Zforce-unstable-if-unmarked so this does not allow items from these crates to be used on stable.

For example, some users write benchmarks using libtest and have written extern crate test without the #[cfg(test)] attribute to load the crate. There may be other niche uses of unstable sysroot crates that this enables to continue on nightly toolchains.

An allowlist of builtin crate names isn’t used here to avoid Cargo needing to hardcode the names of many crates in the sysroot which are inherently unstable.

Proposal

Why not use noprelude for explicit builtin dependencies?

Explicit builtin dependencies without the noprelude modifier behave more consistently with other dependencies specified in the Cargo manifest.

This is a trade-off, trading consistency of user experience with special-casing in Cargo. Cargo would have to handle implicit vs explicit dependencies differently. An explicit dependency on the standard library will behave similarly to other dependencies in their manifest, but the behaviour will be subtly different than with implicit builtin dependencies (where extern crate is required).

See the rustc documentation for more details on noprelude.

Proposal

Why not require builtin dependencies instead of supporting implicit ones?

Requiring explicit builtin dependencies over an edition would increase the boilerplate required for users of Cargo and make the minimal Cargo.toml file larger.

Supporting implicit dependencies allows the majority of the Rust ecosystem from having to make any changes - no_std crates (or crates with a std feature) will still benefit from adding explicit dependencies as allow them to be easily used with no_std targets but users can still work around any legacy crates in the graph with build-std.crates.

Proposal

Why disallow renaming standard library dependencies?

Cargo allows renaming dependencies with the package key, which allows user code to refer to dependencies by names which do not match their package name in their respective Cargo.toml files.

However, rustc expects the standard library crates to be present with their existing names - for example, core is always added to the extern prelude.

Alternatively, a mechanism could be added to rustc so that it could be informed of the user’s names for builtin crates.

Proposal

Why disallow source replacement on builtin packages?

Modifying the source code of the standard library in the rust-src component is not supported. Source replacement of the builtin source could be a way to support this in future but this is out-of-scope for this proposal.

See Allow builtin source replacement.

Proposal

Why not permit overriding dependencies with replace or paths?

Similarly to source replacement, easing modification of the standard library sources is out-of-scope for this proposal.

Proposal

Why add standard library dependencies to Cargo.lock?

Cargo.lock is a direct serialisation of a resolve and that must be a two-way non-lossy process in order to make the Cargo.lock useful without doing further resolution to fill in missing builtin packages.

Cargo will nevertheless need to support lockfiles without builtin dependencies as Cargo does not force new lockfile versions.

Proposal

Why pass -Zcrate-attr=no_std to rustc?

The introduction of explicit dependencies means that there are now two ways to indicate whether a crate depends on the standard library - builtin dependencies in the Cargo manifest and the #![no_std] attribute. This isn’t ideal.

By passing -Zcrate-attr=no_std to rustc (or some equivalent), users no longer need to specify the attribute explicitly in their source code. The attribute can still be explicitly specified this way, which is useful when rustc is used without a build system like Cargo. Other build systems can similarly pass -Zcrate-attr=no_std if emulating how build-std works in Cargo.

Removing or replacing #![no_std] as a mechanism is left as a follow-up to avoid introducing a language change in this RFC - see Replace #![no_std] as the source-of-truth for whether a crate depends on std.

Proposal

Why unstably permit patching of the standard library dependencies?

Being able to patch builtin = true dependencies and replace their source with a path dependency is required to be able to replace rustc_dep_of_std. As crates which use these sources cannot be published to crates.io, this would not enable a usable general-purpose mechanism for crates to modify the standard library sources. This capability is restricted to nightly toolchains as that is all that is required for it to be used in replacing rustc_dep_of_std.

Patches

Why limit enabling standard library features to an unstable feature?

If it were possible to enable features of the standard library crates on stable then all of the standard library’s current features would immediately be held to the same stability guarantees as the rest of the standard library, which is not desirable. See Allow enabling/disabling features with build-std

Features

Why default to public for implicit standard library dependencies?

There are crates building on stable which re-export from the standard library. If implicit standard library dependencies were not public then these crates would start to trigger the exported_private_dependencies lint when upgrading to a version of Cargo with a implicit standard library dependency.

Public and private dependencies

Why default to private for explicit standard library dependencies?

All other explicitly written dependencies in the Cargo manifest are private-by-default. This RFC tries to make builtin dependencies as consistent with other dependencies in the manifest as possible (e.g. in Why not use noprelude for explicit builtin dependencies?).

Public and private dependencies

Why include standard library crates in Cargo’s index format?

When Cargo builds the unit graph - roughly speaking, think of this as the compiler invocations it will eventually make - it queries the dependency resolver as to which dependencies a given crate has. The dependency resolver only looks at the source of a dependency, in many cases a registry source (i.e. a dependency from crates.io). In practice, this is the entry in the Cargo index for that dependency. As this is the information that is directly available, that a crate depends on the standard library should be reflected in the index entry.

Alternatively, the Cargo manifests of dependencies would need to be parsed in order to determine whether they have standard library dependencies as the index entry would be insufficient.

Registries

Why add a new key to Cargo’s registry index JSON schema?

Cargo’s registry index schema is versioned and making a behaviour-of-Cargo-modifying change to the existing deps keys would be a breaking change. Each package is published under one particular version of the schema, meaning that older versions of Cargo cannot use newer versions of packages which are defined using a schema it does not have knowledge of.

Cargo ignores packages published under an unsupported schema version, so older versions of Cargo cannot use newer versions of packages relying on these features (though this would be true because of an incompatible Cargo manifest anyway). New schema versions are disruptive to users on older toolchains, as the resolver will act as if a package does not exist. Recent Cargo versions have improved error reporting for this circumstance.

Some new fields, including rust-version, were added to all versions of the schema. Cargo ignores fields it does not have knowledge of, so older versions of Cargo will simply not use rust-version and its presence does not change their behaviour.

Existing versions of Cargo already function correctly without knowledge of crate’s standard library dependencies. A new top-level key will be ignored by older versions of Cargo, while newer versions will understand it. This is a different approach to that taken when artifact dependencies were added to the schema, as those do not have a suitable representation in older versions of Cargo.

The obvious alternative to a builtin_deps key is to modify deps entries with a new builtin: bool field and to increment the version of the schema. However, these entries would not be understood by older versions of Cargo which would look in the registry to find these packages and fail to do so.

That approach could be made to work if dummy packages for core/alloc/std were added to registries. Older versions of Cargo would pass these to rustc via --extern and shadow the real standard library dependencies in the sysroot, so these packages would need to contain extern crate std; pub use std::*; (and similar for alloc/core) to try and load the pre-built libraries from the sysroot (this is the same approach as packages like embed-rs take today, using path dependencies for the standard library to shadow it).

Registries

Why can builtin_deps shadow other packages in the registry?

While crates.io forbids certain crate names including std, alloc and core, third party registries may allow them without a warning. The schema needs a way to refer to packages with the same name either in the registry or builtin, which builtin_deps allows.

builtin_deps names are not allowed to shadow names of packages in deps as these would conflict when passed to rustc via --extern.

Registries

Unresolved questions

The following small details are likely to be bikeshed prior to this part of the RFC’s acceptance or stabilisation and aren’t pertinent to the overall design:

What syntax is used to identify dependencies on the standard library in Cargo.toml?

What syntax should be used for the explicit standard library dependencies? builtin = true? sysroot = true (not ideal, as “sysroot” isn’t a concept that we typically introduce to end-users)?

Proposal

What is the format for builtin dependencies in Cargo.lock?

How should builtin deps be represented in lockfiles? Is builtin = true appropriate? Could the source field be reused with the string “builtin” or should it stay only as a URL+scheme?

Proposal

What syntax is used to patch dependencies on the standard library in Cargo.toml?

[patch.builtin] is the natural syntax given builtin is a new source, but may be needlessly different to existing packages. This mechanism exists only for internal purposes so the exact syntax isn’t especially important.

Patches

Should implicit standard library dependencies default to public?

Implicit standard library dependencies defaulting to public is a trade-off between special-casing in Cargo and requiring that any user with a dependency on the standard library who re-exports from the standard library manually declare their dependency as public.

Public and private dependencies

Should explicit standard library dependencies default to private?

Explicit standard library dependencies defaulting to private is a trade-off between consistency with other dependencies in the manifest and implicit standard library dependencies.

Public and private dependencies

Should we support build-dependencies?

Allowing builtin dependencies to be used in dependencies and dev-dependencies but not in build-dependencies is an inconsistency.

However, supporting builtin dependencies in build-dependencies would permit no-std build scripts. It is unclear whether supporting no-std build scripts would be desirable. Not supporting build-dependencies initially is a safe default that can be changed later if this is deemed necessary, without breaking any existing users.

dev-dependencies and build-dependencies

Should parsed manifests be used instead of the registry index?

An alternative to changing the registry index would be checking the parsed manifests of dependencies - which are downloaded and available when the unit graph is being built (and used for other Cargo features).

Registries

Should cargo metadata include the standard library’s dependencies?

cargo metadata is used by tools like rust-analyzer to determine the entire crate graph and would benefit from knowledge of the standard library’s dependencies, but this leaks internal details of the standard library and is counter to the intent behind opaque dependencies.

This is primarily a compatibility concern, though were builtin dependencies to be included in the metadata, it is expected that it would not be too disruptive, as:

  • cargo pkgid and cargo build --message-format will unconditionally use the new pkgid spec anyway;
  • cargo_metadata does not currently parse the pkgid spec so this won’t break all users;
  • and this will also show up in the source which is still opaque and cargo_metadata does not parse

Therefore this would only be a problem for users who are parsing the pkgid specs using cargo-util-schemas or their own parser, which is unlikely to be disruptive.

Cargo subcommands

What should the exact format of the pkgid spec for builtin dependencies be?

The format for builtin dependencies of cargo pkgid can be changed prior to stabilisation and does not need to match what is proposed in this RFC exactly.

Cargo subcommands

Prior art

See the Background and History of the build-std context RFC.

Future possibilities

This RFC unblocks fixing rust-lang/cargo#8798, enabling no-std crates from being prevented from having std dependencies.

There are also many possible follow-ups to this part of the RFC:

Replace #![no_std] as the source-of-truth for whether a crate depends on std

Crates can currently use the crate attribute #![no_std] to indicate a lack of dependency on std. #![no_std] serves two purposes - it stops the compiler from adding std to the extern prelude and it prevents the user from depending on anything from std accidentally.

rustc’s default behaviour of loading std when not explicitly provided the crate via an --extern flag must be preserved for backwards-compatibility with existing direct invocations of rustc.

Ideally, #![no_std] would be removed entirely and the compiler would only load std if it were used, but this isn’t possible with the current compiler implementation.

Therefore, an explicit flag or attribute is necessary to tell rustc whether to load std. -Zcrate-attr=no_std could be replaced with a explicit compiler flag --no-std (naming subject to bikeshed) that Cargo and other drivers of rustc could use. #![no_std] could be removed over an edition alongside addition of builtin dependencies to the Cargo manifest.

As removing #![no_std] could easily be left to a follow-up, and is a change to a stable attribute in the surface language (which would require language team approval), it isn’t included in this proposal to keep scope and the number of required approvals small.

Proposal

Allow unstable crate names to be referenced behind cfgs without requiring nightly

It is possible to allow builtin dependencies on unstable crate names to exist behind cfgs and for the crate to be compiled on a stable toolchain as long as the cfgs are not active. This is a trade-off - it adds a large constraint on when Cargo can validate the set of crate names, but would enable users to avoid using nightly or doing MSRV bumps.

Proposal

Allow builtin source replacement

This involves allowing the user to blanket-override the standard library sources with a [source.builtin] section of the Cargo configuration.

As rationale-source-replacement details it is unclear if users need to do this or if it’s even something the Rust project wishes to support.

Proposal

Remove rustc_dep_of_std

With first-class explicit dependencies on the standard library, rustc_dep_of_std is rendered unnecessary and explicit dependencies on the standard library can always be present in the Cargo.toml of the standard library’s dependencies.

The core, alloc and std dependencies can be patched in the standard library’s workspace to point to the local copy of the crates. This avoids crates.io dependencies needing to add support for rustc_dep_of_std before the standard library can depend on them.

Proposal

Allow enabling/disabling features with build-std

This would require the library team be comfortable with the features declared on the standard library being part of the stable interface of the standard library.

The behaviour of disabling default features has been highlighted as a potential cause of breaking changes.

Alternatively, this could be enabled alongside another proposal which would allow the standard library to define some features as stable and others as unstable.

As there are some features that Cargo will set itself when appropriate (e.g. to enable or disable panic runtimes or compiler-builtins/mem), Cargo may need to always prevent some otherwise stable features from being toggled as it controls those.

Features

Allow local builds of compiler-rt intrinsics

The c feature of compiler_builtins (which is also exposed by core, alloc and std through compiler-builtins-c) causes its build.rs file to build and link in more optimised C versions of intrinsics.

It will not be enabled by default because it is possible that the target platform does not have a suitable C compiler available. The user being able to enable this manually will be enabled through work on features (see Allow enabling/disabling features with build-std). Once the user can enable compiler-builtins/c, they will need to manually configure CFLAGS to ensure that the C components will link with Rust code.