--- toc: maxLevel: 99 --- # Summary The crates that form Rust's standard library are managed differently than how Cargo handles other crate dependencies. They are shipped as compiled artifacts and because these are expected to handle every use case of a particular target this causes issues for certain users. This Project Goal aims to propose a design to build standard library crates locally, similarly to other dependencies. In 2019 in [initial implementation] of build-std was merged, an [RFC] for std-aware Cargo was closed and the std-aware Cargo [working group] was formed. This project goal aims to produce a continuation of the closed RFC using the issues listed in the working group as a guide. This document will link to issues there for additional context. Low issue numbers below 100 are links to the Working Group issues, issues in the order of #10,000 are generally Cargo issues, and issue numbers in the order of #100,000 are Rust issues. [initial implementation]: https://github.com/rust-lang/cargo/pull/7216 [RFC]: https://github.com/rust-lang/rfcs/pull/2663 [working group]: https://github.com/rust-lang/wg-cargo-std-aware/ # Motivation In today's Rust environment, `core` and `std` are shipped as precompiled objects. This was done for a number of reasons such as faster compile times and a more consistent experience for users of these dependencies. This design has served the bulk of users fairly well. However there are a number of less common uses of Rust that are not well served by this approach. Examples include: ## Long-term ### Supporting tier 3 targets and custom ("`.json`") targets Builds are not distributed for these targets and so they often require building at least `core` through Cargo or from the `rust-lang/rust` tree. ### Using codegen flags to optimize to `core` or `std` A common example is `opt-level = 's'` or `z`, with `panic = "abort"`. This is common for embedded users who often want smaller binaries. All users can benefit from performance or security optimisations through codegen flags like `target-cpu` or `target-feature`. Miri usage also requires building the standard library with mir-opts disabled and MIR embedded in rmeta/rlib files. ### Supporting [Target Modifiers](https://github.com/rust-lang/rfcs/pull/3716) Target modifiers are a [future](https://github.com/rust-lang/rust/issues/136966) class of compilation options that affect the ABI or otherwise require full coverage of the binary such as santizers (CFI in particular), target-features like softfloat/SVE or assorted flags like `-Zfixed-x18`. Users changing flags to be classed as Target Modifiers will need to rebuild all required standard library crates in order to avoid errors. Standard library build support has been cited as a blocker for stabilising flags in this class, such as with [`-Zbranch-protection`](https://rust-lang.zulipchat.com/#narrow/channel/343119-project-exploit-mitigations/topic/CFI.20option.20stabilisation/near/396152066). ### Modifying `core` or `std` through the use of [feature flags](https://github.com/rust-lang/rust/blob/8231e8599e238ff4e717639bd68c6abb8579fe8d/library/std/Cargo.toml#L88) ([#4](https://github.com/rust-lang/wg-cargo-std-aware/issues/4)) Feature flags unblock a variety of nice uses across many different classes of users. They allow reducing binary size for embedded users through disabling `backtrace` or enabling `optimize_for_size` or `panic_immediate_abort`. Enabling `profiler` is required for PGO ([#68](https://github.com/rust-lang/wg-cargo-std-aware/issues/68)). ### Modifying the source code of the standard library Some platforms may wish to modify the source code of standard library but do not or cannot have a target added upstream just for them. Some lower-tier targets may also wish to patch some standard library crates.io dependencies in order to fix them for their target (for a variety of reasons, including that some projects may not wish to support niche platforms). One example is [Apache's SGX SDK](https://github.com/apache/incubator-teaclave-sgx-sdk) which replaces some standard library and ecosystem crates with forks or custom crates for a custom `x86_64-unknown-linux-sgx` target. ## Related Interests We are not designing for these but we should keep them in mind as they may influence designs we come up with and how we evaluate which design to select. ### `no_std` metadata Right now, there isn't a way to tell what crates are `no_std` without digging into their features. If its unconditionally `no_std`, then it requires an inspection of the source code. It may be desirable for a Cargo mechanism to choose whether std is enabled to supersede `no_std`. ### `no_std` dependency linting Right now, it is easy to accidentally pull in a dependency or a feature that needs `std` (e.g. https://github.com/facet-rs/facet/issues/258). ### Caching / pre-built monolithic dependencies In the C world, you have: - light headers-only libraries that your build environment affects - heavy dynamic libraries that are opaque in terms of their build environment and their dependencies In Rust, we operate mostly like the first model except for `std`. `build_std` may add functionality to Cargo that is more like the second model which could be a test bed for eventually offering this model to users - currently referred to as "opaque dependencies" for Cargo. This would make caching or pre-built dependencies more likely to get cache hits (compared to https://github.com/rust-lang/cargo/issues/5931) as well as potentially other benefits. # Suggested scope Build-std has, in some form, been around for almost 10 years at this point without reaching much consensus on a design yet. A big reason for this is the huge potential scope for the feature - as will be discussed in [Prior Art](#prior-art) much of its past discussion is very wide in its reach. There is a constant tossup between idealism and the reality of today. Many solutions to a problem impact other potential solutions which combined with the potential size of the feature makes it very hard to design a complete solution to build-std all at once. It is also difficult to tackle individual issues on the [Working Group issue tracker](https://github.com/rust-lang/wg-cargo-std-aware/issues) piecemeal without an overarching plan for how the basic use cases will function. For this reason the RFC aiming to be produced as a part of this project goal will aim to push for stabilization of a fairly minimal solution that should satisfy most of the points in [Motivation](#Motivation) for most users with the potential for expanding the feature post-stabilization. The [Working Group issue tracker](https://github.com/rust-lang/wg-cargo-std-aware/issues) is a comprehensive list of all facets of the problem discovered so far - many of which are currently tagged as a "Stabilization Blocker" or as "Plan Before Stabilization". The tracker should be used as the full list of ideal requirements and any solution proposed in the RFC should not, with a reasonable degree of knowledge, block implementation of any of those requirements. These below points are my own suggested scope for this initial stabilistaion push based on the features' potential use cases balanced against their inherent complexity. All are subject to change and this list may be expanded when it comes to the RFC. ## In scope - Building the standard library from source with custom RUSTFLAGS and making it transparently available to rustc. - Changing the set of standard library crates built - This is required as some targets fundamentally cannot support `std` - In addition, building unneeded crates worsens compilation times. ## Potentially in scope These points are ripe for further exploration at this point. - Changing the default feature flags for the standard library - While not a core use case this is nice for many different users. I feel it is worth exploring deeper at this point. - Automatically enabling `build-std` when it is useful, and determining the set of crates to build. - Building `std` from source is confusing to some users and is required for certain scenarios - user friction could be drastically reduced if this is possible. ## Out of scope - Non-critical Cargo features that don't currently work with a precompiled standard library but might be expected to work with a regular Cargo dependency built from source, such as `cargo vendor` - I'm not aware of any strong use cases that have emerged for features like this, but they seem like a likely candidate to build in after stabilisation. I'm interested in hearing cases for individual features that may fall under this category. - Some other examples include `-p` options for commands like `build` and `clean`. - Modifying the source of the standard library crates. - While this seems a small step from building std crates locally, the consequences seem fairly large and involve project team consensus. I don't want to distract from the core use cases outlined above. - From a technical standpoint, this currently works (and is indeed difficult to prevent), but Cargo will not trigger rebuilds on source code changes without a `cargo clean` first. - Patching standard library dependencies - I believe the main use case for this may be fixing up the standard library for a tier 3 or custom target. - It is proposed to be out of scope for similar reasons to the above, but the implementation and UI considerations for this feature are much larger. # Prior Art Those who wish to know how the current `-Zbuild-std` flag functions can skip to the [bottom section](#wg-cargo-std-aware-amp--Zbuild-std). ## 2015 RFC In 2015, an [RFC written by Ericson2314](https://github.com/rust-lang/rfcs/pull/1133) was opened. It proposed making Cargo aware of standard library dependencies by allowing the user to specify them in `Cargo.toml` and build them as regular dependencies. This included unique syntax and behaviour for cargo to specify that a dependency belongs to the standard library and to handle backwards compatibility with crates that do not specify their standard library dependencies. After much discussion the RFC was closed for a number of reasons - a lack of time from the author and the development of Xargo were cited as reasons. ## Xargo In 2016 [Xargo](https://github.com/japaric/xargo) was created as an unofficial experiment to rebuild the sysroot before building user crates (as opposed to building std crates as regular dependencies). The project was successful and proved useful to users at the time. A [request to merge similar functionality into Cargo](https://github.com/rust-lang/cargo/issues/4959) was opened in 2018 and led to the closure of the previous RFC. The approach is largely easier to implement than the previous proposal but [concerns were raised](https://github.com/rust-lang/cargo/issues/4959#issuecomment-374015022) over building the sysroot separately including the ongoing maintenance cost of building standard library crates differently to user ones and the build time parallelism left on the table when considering the user's dependency graph too. The concept of the sysroot as a whole (over explicit dependencies via `--extern`) was also questioned. The request proposed a user interface involving adding fields to the `.cargo/config` file to explicitly specify bootstrapping stages, though this didn't gain much support and was considered brittle and user-unfriendly. ## 2019 RFC In 2019, an [RFC written by James Munns](https://github.com/rust-lang/rfcs/pull/2663) was published. The proposal was much more comprehensive than previous approaches, going into detail on the interface and functionality of the new features, but was light on implementation details required for Cargo. It proposed inferring whether to build standard library crates from the Cargo profile and specified a method to declare crate features in the user's `Cargo.toml`. It proposed allowing the user to provide their own custom source versions of standard library crates by declaring a `[patch.sysroot]` section of the `Cargo.toml`. It also proposed stabilising a Target Specification Format ("JSON targets") - an idea that has since been [shown to be difficult](https://github.com/rust-lang/rust/issues/71009). The [proposal was closed](https://github.com/rust-lang/rfcs/pull/2663#issuecomment-513494398) shortly after it became clear the work was larger than originally thought, though no large concerns were raised over the design. ## wg-cargo-std-aware & `-Zbuild-std` When the 2019 RFC was closed, the [`cargo-std-aware` working group](https://github.com/rust-lang/wg-cargo-std-aware/) was created with a comprehensive issue tracker documenting various considerations for build-std. Shortly after, an [MVP for `-Zbuild-std` was merged](https://github.com/rust-lang/cargo/pull/7216) that formed the basis of the implementation still used today. The implementation of the MVP will be described in the following sections. ### UI A new flag, `[-Zbuild-std](https://doc.rust-lang.org/cargo/reference/unstable.html#build-std`, has been added to Cargo which enables the building of standard library crates. By default, `std` and all of its dependencies are built, and `test` is built too if tests are being run. Cargo [will attempt](https://github.com/rust-lang/cargo/blob/c7be0601a74a01f3f62e6259292c15d2f66cc531/src/cargo/core/compiler/standard_lib.rs#L18) to fill in some crate dependencies. Optionally a set of standard library crates to be built can be passed if needed, though this is considered an escape hatch to work around bugs - the arguments to the flag are fundamentally unstable since the crates in the standard library are not fixed and the intent is that it's rarely needed. The most common need for them today is to specify building `panic_abort`. A second flag, `[-Zbuild-std-features](https://doc.rust-lang.org/cargo/reference/unstable.html#build-std-features)`, has also been added and allows overriding the default feature set of the standard library. Like the arguments to `-Zbuild-std`, this flag is fundamentally unstable as its behaviour changes if an item in the standard library is moved behind a new default feature. It is commonly seen used to enable `panic_immediate_abort` though there are other uses. A `rustup` component, `rust-src`, is available in order to make the standard library sources available. The user must perform `rustup component add rust-src` to download the sources for their current toolchain, which are visible in the sysroot (under `lib/rustlib/src/`). The component consists of the `library/` directory plus`src/llvm-project/libunwind/`. ### Mechanism When Cargo is constructing an in-memory workspace for the user's project a separate workspace is constructed for the standard library sources. Speaking roughly, this workspace is then resolved (using the existing `Cargo.lock` file) and the resolve is essentially combined with the user's resolve to produce a `UnitGraph` (a dependency graph of things to build) with the user's Units depending on the standard library's Units. Some additional work is done to deduplicate Units across the graph and then this `UnitGraph` is used to drive work (usually `rustc` invocations) as usual. This approach allows for build-time parallelism and sharing of work between the two separate resolves but does involve a lot of `build-std`-specific logic in the resolver - an important and complicated part of Cargo. This design is [arguably not a long-term solution](#Unified-vs-separate-resolution-64) though how to perform this work in a single resolve is not clear at this point. Issue [#38](https://github.com/rust-lang/wg-cargo-std-aware/issues/38) shows a possible way of mitigating bugs from ths approach. Modifications to the standard library are not supported, either through source code modifications (rebuilds aren't triggered when the `rust-src` component is modified) or through `[patch]` in the `Cargo.toml`. # Large open questions Most of these questions are core to the idea of build-std and affect all use cases, with the exception of the final question regarding `std` Cargo features. They are not a complete list of problems to solve - rather, user-facing questions to answer that will guide further decisions. The main goal of this section is to outline the problems rather than try to solve them. ## Is the standard library special? There is a clear want for the standard library to behave more like a regular crate dependency - doing so would reduce Cargo's maintenance overhead significantly and will likely reduce user friction with the feature. Past designs and implementations have tangled with the reality that the standard library behaves nothing like a regular crate dependency right now and changing that is rather hard. Deciding whether the standard library should remain special has a large impact on Cargo's internal implementation as well as an effect on the UX ([#43](https://github.com/rust-lang/wg-cargo-std-aware/issues/43)) of the feature. The standard library is currently special for a number of reasons: - It lives in the global sysroot and `rustc` looks for it there by default. - This is partially solved by the work ehuss did on `--extern=noprelude:` ([#49](https://github.com/rust-lang/wg-cargo-std-aware/issues/49)) which allow for explicitly specifying artifacts for the standard library. - Rust will still look in the global sysroot if it needs a crate that has not been passed with an `--extern` flag which leads to confusing "duplicate item" errors at times ([#71](https://github.com/rust-lang/wg-cargo-std-aware/issues/71), among many others). This can be solved with a mechanism to disable this sysroot lookup ([#31](https://github.com/rust-lang/wg-cargo-std-aware/issues/31)), such as a new `rustc` flag. - The standard library is versioned with the compiler - This means that their Cargo crate version number is effectively meaningless - The standard library and some of it's crates.io dependencies requires nightly features - This is valid in general because the standard library version always matches the compiler version and standard library dependency versions are tracked in it's Cargo.lock file. - This seems really hard to change but is easy to mitigate. Handling this problem on stable toolchains is covered by the [below question](#How-does-a-stable-compiler-build-standard-library-crates-with-nightly-features) - The standard library must be built with the dependency versions in its lockfile. - This is because dependencies may use nightly features and that changing dependency versions may invalidate careful testing performed on Rust releases. - In addition, any crates.io dependency may push a new broken minor version which, if pulled into the build by Cargo's default "greedy" version resolution strategy, could cause breakage on a large scale. - This has a significant cost for Cargo's resolution which is explained in more detail [below](#Unified-vs-separate-resolution-64). - The user specifies their dependency on standard library crates in source code - This should ideally be a Cargo feature rather than directives like `#![no_std]`, `#![no_core]` or `extern crate alloc` which cannot be read by Cargo. - `core` defines lang items - This doesn't mean extra requirements for `build-std` given that nightly features can be used but does make `core` code quite different from most user code. A future where standard library crates are not special would be really nice but it seems most likely that they will have to be somewhat special, if less so than today. An [alternative proposal](https://hackmd.io/@davidtwco/rkYRlKv_1x) to a Cargo solution in rustup which embraces the fact that standard library crates are special has been explored but hit a roadblock on a niche interaction with artifact deps ([#9096](https://github.com/rust-lang/cargo/issues/9096)). ## How does a stable compiler build standard library crates with nightly features? The standard library and its dependencies make use of nightly features, and the standard library is currently required to be built with the unstable "[-Zforce-unstable-if-unmarked](https://github.com/rust-lang/cargo/blob/df377583d21132e787e0006a23f695d292d993e8/src/cargo/core/compiler/mod.rs#L1207)" `rustc` flag. This poses a problem post-stabilisation when `rustc` could be a stable compiler and so an "escape hatch" is needed to allow nightly features on these compilers. Non-Cargo users like Rust for Linux want an approved method for doing this too as they build `core` from source. It's valid for a `rustc` user to enable nightly features on a stable compiler when the crate sources and compiler agree on the set of nightly features available - as is the case with `build-std` where the standard library sources (and the dependencies it specifies) have been tested against the version of the compiler in use. A solution must: - Be applicable to standard library dependencies too, which may also use nightly features. Having `rustc` check the crate names against a pre-approved list of standard library crates is not acceptable. - Not be open to abuse. Some users, once learning about this feature, may use it incorrectly and potentially expose this to the ecosystem. A new stable compiler flag may be considered inappropriate for this reason. Currently this works via `RUSTC_BOOTSTRAP`, which is a satisfactory solution if blessed by the compiler team for these purposes. This variable is commonly used by Rust contributors and so may have to stay present if another solution is chosen. Some other possible solutions include: - If standard library dependencies were vendored an undocumented file could be included with the crate sources and detected by `rustc`. - Rustc could check if crate sources are in the sysroot - anything in the sysroot is already specific to that compiler version. This would necessitate vendoring standard library dependencies too. This might be too cumbersome for non-Cargo users like Rust for Linux. ## How are standard library crates distributed? [#11](https://github.com/rust-lang/wg-cargo-std-aware/issues/11) Currently this is the `rust-src` rustup component which packages the `library/` and `src/llvm-project/libunwind/` directories. This couples the structure of the `rust-lang/rust` repo with the implementation of Cargo. Continuing to do so may make it harder to change the structure of the repository, particularly due to Cargo's status as a submodule with its own CI which makes it hard to make simultaneous changes to both repos ([#85](https://github.com/rust-lang/wg-cargo-std-aware/issues/85) discusses mitigating this issue from a testing standpoint). Concern should also given to dependencies of the standard library on crates.io - what happens if a version relied on by a version of Rust is removed? Vendoring the standard library dependencies before distribution would help in this scenario. This question also has an impact on how Cargo loads `std` crates and resolves them to generate a Unit Graph (a collection of things to build). Other options to `rust-src` include distributing `.crate` tarballs for standard library crates. ### Source code verification It might be desirable to ensure that the crate sources or the standard library's resolve haven't been changed from the ones distributed. The main use case in mind here is avoiding accidental modifications of the `rust-src` component - with the current implementation of `-Zbuild-std` accidental modifications will be very difficult to track down and are only removed with a `rustup update`. This could be achieved with some checksums, either included with the `rust-src` component or even embedded in `rustc` (which would make it very hard to modify the source files even intentionally). ## How does the user enable building crates from source? [#43](https://github.com/rust-lang/wg-cargo-std-aware/issues/43) Some aspects to balance in this decision include: - Making `build-std` as transparent to the user as possible - many situations where it is required are not currently obvious to end users (initiatives such as [Target Modifiers](https://github.com/rust-lang/rfcs/pull/3716), expanded on below, and Ralf's push for ABI soundness help here). - Making `build-std` configuration as easy to the user as possible. Currently certain scenarios such as changing a target's default panic strategy ([#29](https://github.com/rust-lang/wg-cargo-std-aware/issues/29)) from unwind to abort require unintuitive Cargo options to build `panic_abort` and set the `panic_immediate_abort` feature. - Enabling `build-std` comes with a compile-time cost, significantly worsening one of Rust's most common complaints. In particular this is an issue when many user-specified dependencies depend on `std`, blocking compilation on the building of that one crate. - Quite often users will iterate on a build configuration, causing many rebuilds of `std`, but once this build configuration is decided built `std` artifacts can be reused in future builds. - Release builds are less sensitive to build times and more sensitive to runtime performance than debug builds. - The future of the standard library - This UI, once stabilised, cannot be changed (though could be superseded) and may restrict `build-std` in the future or even decisions about the standard library itself. - For example, a stable set of arguments to a `build-std` flag akin to the crates in the standard library today may restrict changing the crate structure of the standard library in the future. Previous solutions to this problem attempted to specify standard library dependencies in the user's `Cargo.toml` file ([#5](https://github.com/rust-lang/wg-cargo-std-aware/issues/5), [RFC #2663](https://github.com/rust-lang/rfcs/pull/2663)), explained below. ### 2015 RFC proposal The 2015 RFC is now almost 10 years old - Rust has changed a lot in this time and so has the context of this RFC. I've done my best to translate this to today's context and remove parts which are no longer relevant but may have made mistakes. The original RFC proposal intended the user to specify a dependency on the standard library crates with a directive like `std = { version = "1.10", stdlib = true }`. The version field is required, but could be set to `'*'` (today this field would duplicate the `rust-version` field). It is illegal to patch a crate with a `stdlib` dependency partially because the initial rollout was planned to be for regular dependencies only, not dev or build dependencies. Since every project implicitly depends on std Cargo would inject dependencies for existing packages that do not specify a `stdlib` dependency themselves. The `core` crate itself would use an unstable key to opt out of adding these implicit dependencies. The implicit dependencies would look like this if expressed explicitly using syntax the RFC proposed: ``` [dependencies] core = { version = "^1.0", stdlib = true } std = { version = "^1.0", stdlib = true } [dev-dependencies] core = { version = "^1.0", stdlib = true } std = { version = "^1.0", stdlib = true } test = { version = "^1.0", stdlib = true } [build-dependencies] core = { version = "^1.0", stdlib = true } std = { version = "^1.0", stdlib = true } ``` Internally, Cargo would inject any implicit deps into the users `Cargo.toml`. Any `stdlib = true` dependencies are given a [`SourceId`](https://doc.rust-lang.org/nightly/nightly-rustc/cargo/core/struct.SourceId.html) pointing into either the standard library sources (in the sysroot) or the "sysroot binary mock source" which is generated by finding the library binaries in the sysroot. The library sources are used to resolve the `stdlib=true` dependencies as normal (a solution which does not work as-is today as standard library dependency versions [cannot be changed from its lockfile](#Low-level-requirements)). The RFC does not go into detail on how to choose if both binary artifacts and library sources are available but does detail how some settings, like overriding Cargo features, might make the prebuilt binaries invalid. Once a build plan or unit graph has been constructed stdlib dependencies with sources can be executed as normal. Cargo would pass prebuilt binaries to `rustc` using `--extern` (unlike normal dependencies which are passed using `-L` and Cargo's output directory). Cargo would pass `--sysroot=` (the empty path) to avoid `rustc` falling back to the sysroot (which I believe doesn't work today on some targets that rely on non-stdlib artifacts in the sysroot). Whenever prebuilt binaries are present Cargo passes `-L dependency=<sysroot>/lib/rustlib/<target-triple>/lib` too in order for `rustc` to find transitive deps for binary dependencies and also because "binary mock source crates may in fact be transitive deps of the crates built from source" (it's not clear to me if this sometimes invalidates the unsetting of the sysroot, such as if `extern crate std` is used). `stdlib` dependencies are excluded from the lockfile as including version numbers restricts the compiler version. The RFC notes that "the sysroot binary mock source is a complicated special case spanning many parts of Cargo" as a major drawback of the design. Some feedback on the RFC's design that was not clearly resolved includes: - It's quite cumbersome for best practice to be all crates on crates.io to declare `std = { stdlib = true }` - How do you explain to a new user that you "should" do this, but everything will work just fine if you don't do it? - Its not apparent why it's useful unless you're rebuilding std. - It's not clear what the RFC actually enables on it's own (i.e. without the ability to build the standard library) - The RFC is insufficient to easily enable building the standard library in the future - ### 2019 RFC ### The status quo Right now, from Cargo's perspective, every crate may depend on every standard library crate and `rustc` will pull them in as needed from the sysroot. Cargo cannot see crates marked as `#![no_std]` or `#![no_core]` (nor their `extern crate` directives) and nor can it tell if the current target does not support `std`. It could tell if the crate requires `test` and has some visibility on the dependency on `panic_[abort/unwind]` from the current [profile](https://doc.rust-lang.org/cargo/reference/profiles.html#panic), but for all intents and purposes it is unaware of the standard library. Cargo's initial MVP implementation of build-std relies on the user passing an unstable `-Zbuild-std` flag ### Alternatives The current `-Zbuild-std` flag implementation is not suitable for stabilisation as it exposes standard library crates names directly which the library team have not committed to being stable with some actively wanting to change in the area - there are [discussions](https://rust-lang.zulipchat.com/#narrow/channel/219381-t-libs/topic/Working.20group.20for.20.22single.20crate.20std.22.20.2F.20.22std.20features.22/with/438907624) around moving the standard library into a single crate with conditional compilation. At minimum this flag requires some stable set of arguments to form an abstraction over the standard library crate structure. To explore this area deeper, lets explore making `build-std` as easy as possible to the user while only focusing on build times to the point where they do not hurt the ease of use this brings. Doing so involves Cargo making the decision on whether to build std crates itself, what crates to build and what crate features to build them with. Such a solution might be possible at this point but requires more design work to ensure this is feasible. The [Target Modifiers](https://github.com/rust-lang/rfcs/pull/3716) RFC proposes marking flags that must be set for all crates for correct usage as target modifiers, ensuring that `rustc` errors if flags are improperly mixed. If Cargo could gain awareness of these flags then it could automatically enable `build-std` if the user changes these flags. When changing other flags, which do not break linking, Cargo could also force rebuilding `std` for release builds and not rebuild during debug builds (which would involve tolerating differences in [fingerprints](https://doc.rust-lang.org/nightly/nightly-rustc/cargo/core/compiler/fingerprint/index.html)) for build time reasons. If Cargo is to determine when to use rebuild standard library crates, it must also determine the set of crates to build. I believe it should be possible to determine something close to the desired set of standard library crates automatically by looking at the Cargo profile, codegen flags supplied and whether the target supports std, though this also needs needs more work. For example: - The Cargo test profile implies building `test` - Whether the target [supports `std`](https://github.com/rust-lang/rfcs/pull/3693) implies whether to build `std` - The panic strategy field in the Cargo profile together with the target's default panic strategy implies whether to build `panic_abort` or `panic_unwind` This must of course all happen over a stable interface with `rustc` - that is, not via a flag like `-Zprint=target-spec-json`. It may be hard to determine whether `alloc` is required without parsing source code but building alloc is generally fairly quick compared to `core` or `std`. ## How does the user modify the crate features? [#4](https://github.com/rust-lang/wg-cargo-std-aware/issues/4) Some maintainers are already in support for stabilising some subset of standard library features, listed in the [sysroot `Cargo.toml`](https://github.com/rust-lang/rust/blob/master/library/sysroot/Cargo.toml). The use cases for this currently include: - Disabling `backtrace` or enabling `optimize_for_size` for code size improvements. - The latter is very new and not currently particularly useful. - Linking in an alternate panic strategy with `panic-unwind` and `panic_immediate_abort` - Enabling `profiler_builtins` for PGO More standard library features may be introduced in the future. These current use cases touch a variety of users from embedded software (which cares about code size) to application-level software (which may care about runtime performance). This has led to me exploring this feature for inclusion in the project goal's RFC despite it the fact that it is arguably not necessary for some common use cases for `build-std`. ### Prior work The [2019 RFC](https://github.com/jamesmunns/rfcs/blob/cargo-the-std-awakens/text/0000-std-aware-cargo.md) proposed specifying the features of individual standard library crates when specifying a dependency on them in the user's `Cargo.toml`, like so: ``` [dependencies.core] default-features = false features = [...] ``` Overriding the default features of a crate would be one of the triggers to force recompilation of the standard library. The RFC also proposed the concept of stable Cargo features which would be allowed on stable compilers while unstable Cargo features were restricted to nightly compilers only. The RFC did not prescribe a mechanism for marking features as stable or unstable. The RFC proposed specifying `core`, `alloc` and `std` in the user's `Cargo.toml` but did not mention other standard library crates like `test` or the panic strategy crates. Patching the sysroot crates was also supported, allowing users to add their own crate features. A number of concerns were raised on the RFC or in response to it: - Stabilising standard library Cargo features greatly increases the number of configurations of the standard library. In theory fully testing these configurations means testing 2^n versions of Rust, where n is the number of stable crate features across the whole standard library. - In theory this number is lower if a crate feature simply adds or removes items from the standard library but some do more complicated things, such as `optimize_for_size`. Testing with all stable features disable or all of them enabled may provide enough coverage. - [Disabling the default feature set](https://github.com/rust-lang/rfcs/pull/2663#issuecomment-499702946) makes it a breaking change to put an existing item behind a new default feature, which may be too restrictive for library contributors. - This concern has been raised more generally in Cargo ([#3126](https://github.com/rust-lang/cargo/issues/3126)) and it seems the standard library would want a similar mechanism. - Some standard library features are currently driven by `bootstrap`, [depending both](https://github.com/rust-lang/rust/blob/97c966bb40756903f8aa13995629128d157f6056/src/bootstrap/src/lib.rs#L707) on `config.toml` settings and the current target. - This means that the set of features that users need to enable to be on parity with the distributed libraries is not obvious. ### Status quo Currently the experimental [`-Zbuild-std-features` flag](https://github.com/rust-lang/cargo/pull/8490) allow for specifying top-level features for the `sysroot` crate (though perhaps unintentionially features for individual crates can be set using an argument like `core/debug_refcell` for example). The flag is "highly unlikely ever to be stabilized" and is present mainly to experiment with and to make the `-Zbuild-std` flag more useful. ### Resolving standard library features The main difference between the 2019 RFC and the current implementation is that standard library features are currently set globally by the top-level crate. There are a few problems with crates in the dependency graph setting standard library features themselves: - This necessitates resolving the standard library and the user's crate together which is difficult to do at this stage (more explanation in [#64](https://github.com/rust-lang/wg-cargo-std-aware/issues/64#issuecomment-2784448221)). - This seems unnecessary because currently [existing `std` features](https://gitub.com/rust-lang/rust/blob/master/library/std/Cargo.toml) only make sense to use with knowledge about the target platform or codegen options used at build time. This makes libraries poorly placed to make decisions about the standard library features used. On the other hand setting features globally is a more restrictive option in general as some libraries may depend on optional items in the standard library, though this is not currently the case. For example, `alloc` currently has an ad-hoc CFG to disable panicking on OOM failure. This was used but Rust for Linux (until they forked `alloc` for other reasons, but the differences may be reconcilable), and is also used by the Windows kernel. In ideal world we would have driver and other low-level OS code on `crates.io` that would be usable by multiple OSes; such crates would want to opt-out of fallibility in `alloc` via their Cargo depencies (see [Zulip](https://rust-lang.zulipchat.com/#narrow/channel/219381-t-libs/topic/no_global_oom_handling.20removal) for some wide-ranging recent discussion on this topic). ### Open questions around features Given this context there are multiple questions to answer as part of the problem of standard library features. - What is the mechanism for marking features as stable? - It could in theory be as simple as prepending "unstable-" to any unstable ones and inserting relevant checks on feature names in Cargo. This comes with a small implementation and maintenance cost. However, the feature names would still be present on stable/beta (as unstable features will be required for building some targets) - is it necessary to hide these in the `rust-src` component to avoid non-Cargo users using them on stable? - There is a more general ask for stable/unstable features ([#10881](https://github.com/rust-lang/cargo/issues/10881)) in Cargo, which of course would be a preferable long term solution. This feature is very unlikely to use prefixes and more likely to introduce an explicit field on features. It is up to the Cargo team to decide whether to support a solution specific to the standard library in the meantime. - How are crate features exposed to the user from Cargo? - In my own opinion the most feasible approach considering the above concerns is a allowing the top-level user to set standard library crate features globally by adjusting features from a baseline default feature set. It is not clear to me how portable this approach will be across targets. - Some features might not need to be exposed to the user from Cargo at all. For example, Cargo could set `panic_unwind` or `panic_immediate_abort` depending on the [profile](https://doc.rust-lang.org/cargo/reference/profiles.html#panic) value (see also [#29](https://github.com/rust-lang/wg-cargo-std-aware/issues/29)). - Are the library team ok with the idea of stabilising some subset of crate features? - Individual features are of course subject to their own individual decisions, but a team commitment to the principle is needed sooner. - The exact ask depends on the user interface we choose to expose to the user. One such as might be that if an item is present under the default feature set then it remains present under the default feature set in the future. # Low-level requirements This section documents requirements found during investigations that don't clearly fit into one of the user-facing questions above; many are related to implementation details. - This feature must be in Cargo. - @davidtwco has [explored](https://hackmd.io/@davidtwco/rkYRlKv_1x) the idea of using rustup to build and manage sysroots, embracing the idea that `std` and related crates are special in Rust. - The approach seemed very appealing but requires Cargo to defer back to rustup to choose a toolchain for building [artifact dependencies](https://github.com/rust-lang/cargo/issues/9096) with a different `std` artifact which does not seem desirable for Cargo. - Some responsibility may be a better fit for `rustc` which is already closer to the standard library and is versioned with it too. However Cargo is needed in order to build the standard library and [configure it correctly](https://github.com/rust-lang/rust/blob/master/library/Cargo.toml) - The standard library must always be built with the same dependency versions as its source `Cargo.lock` file. - This is because some dependencies use nightly features when part of the standard library. - The compiler should not search the sysroot for standard library dependencies when `build-std` is enabled to avoid confusing linkage errors. - However, the compiler should still search there for certain [prebuilt object files](https://github.com/rust-lang/wg-cargo-std-aware/issues/46) or [linkers](https://github.com/rust-lang/wg-cargo-std-aware/issues/47) ## Unified vs separate resolution [#64](https://github.com/rust-lang/wg-cargo-std-aware/issues/64) Today, `-Zbuild-std` works by separately resolving a build plan for the standard library and "userspace" crates, and then combining those build-plans together. An alternative would be to instead jointly resolve all creates in a single resolution. A unified resolution is necessary for having userspace crates' deps influence how the standard library is compiled, such as depending-upon specific standard library crates and specific standard library crates' features. The utility of this is [discussed above](#How-does-the-user-modify-the-crate-features-4). Performing a single resolution is easy; Cargo's default mode of operation (without today's `-Zbuild-std`) is to perform a single resolution. The only part that needs work is to assemble the *inputs* for a single, unified resolution. This means assembling a single lockfile. Such a lockfile is currently impossible to build as the standard library dependency versions cannot be changed from its own lockfile and Cargo does not allow conflicting dependencies with the same major version in the same lockfile in the case the user wants a different version of a `std` dependency. Relaxing this second restriction in general is not valid because it breaks producing a resolve from vendored dependencies in the absence of a lockfile as the resolver cannot distinguish a particular version to choose. Relaxing this requirement in a way specific to the standard library may be possible as the standard library does not export any types from its dependencies and hence cannot leak "conflicting" versions of the same type outside of the standard library. This possibility, of "different" indistinguishable type definitions existing at the same point in Rust code is the reason for the Cargo's resolver's original restriction. As of https://github.com/rust-lang/rust/pull/135501, the standard library is starting to use public and private deps.