Skip to content

build-std: first iteration #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 35 commits into from
Closed

build-std: first iteration #1

wants to merge 35 commits into from

Conversation

davidtwco
Copy link
Owner

@davidtwco davidtwco commented Jun 11, 2025

Copy link

@ehuss ehuss left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow!

Will take me a bit to get through this all.

@davidtwco
Copy link
Owner Author

Addressed the feedback so far, also pushed a bunch more non-context content that I had drafts of - there's plenty of TODO comments and things to fix in those parts, definitely not done yet, but will flesh out more and improve over the coming days.

Copy link

@jacobbramley jacobbramley left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've got a lot more reading to do, but I think my comments so far are mostly local nits, so I'll publish what I've got now. Thanks!

Copy link

@ehuss ehuss left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First of all, thank you for doing the huge amount of work here! My comments may seem negative, but my intent is to try to improve things and warn you about how some of this seems from a first look.


I worry that the scope of this is too large, and that there are a many things in here that are going to be hard sells. There are a lot of breaking changes in here which I feel like people won't find acceptable. I worry that trying to post this as an RFC likely will result in at least a year of discussion before we can get people on board to approve it. I don't want to sound too negative, this is really great stuff! I'm just warning you that this is going to be very difficult.


My confidence is low that this will be able to support all targets out the gate. I would strongly recommend thinking about a slower, more controlled rollout (rust-lang/wg-cargo-std-aware#86).


I'm worried about how this is setting up an expectation that just building the standard library for all targets will "just work" for everyone. I think that is going to be extremely difficult, especially with the other goal of expecting it to match the pre-built standard library. That's why I was strongly recommending that this be an opt-in process, instead of automatically enabled.


There doesn't seem to be much discussion about downloading the source. I don't think we can expect users to manually download the rust-src component. At a minimum, I think it would need to be made a required component (or just ship with rustc). This could also be a breaking change for non-rustup users. This would also be a breaking change for existing vendored users. This could also be a breaking change for users with partial mirrors of crates.io.


I'm a bit worried about the goal of being able to match the pre-built standard library is really high. I think that is going to be very difficult.

For example, I don't see much discussion about optimized compiler-builtins and such.


I don't see much discussion in here about compiler-rt or profiler support.


I'm unclear on how this would work with RUSTFLAGS. Cargo has no knowledge of what those are, and so if the user sets RUSTFLAGS, that seems like it could run into some significant conflicts (particularly with some of the "target modifier" flags that were previously mentioned).


GitHub's UI is choking on this large PR and the large number of comments. I can live with it for now, but I think if the intent is to post this in its entirety on the RFC repo, it could make things difficult. That was also partly why we started the repo at https://github.com/rust-lang/wg-cargo-std-aware/, so that individual threads could be split into issues and we could discuss and come to resolution on them, and to write separate documents and work on them somewhat independently.

[rationale-breakage]: #why-permit-breakage-of-nightly-build-std-users-using-tier-three-targets

As this use case would previously have been using the unstable
`-Zbuild-std=core`, this user must be on a nightly toolchain. This proposal
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't entirely agree with this. I believe there are people building rustc with their own targets, and shipping it themselves, thus having a "stable" version of these targets.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think these targets are "stable" in the sense that the Rust project provides any guarantees for them. If a user is maintaining a downstream fork then they're taking on responsibility to keep that working. I don't think it's especially reasonable to constrain what we can do to save those users from having to fix their fork.

@davidtwco
Copy link
Owner Author

First of all, thank you for doing the huge amount of work here! My comments may seem negative, but my intent is to try to improve things and warn you about how some of this seems from a first look.

I appreciate it! This is absolutely the type of feedback that I'm looking for. It's late here at the moment so I don't have time to reply to your individual points but will try to do so tomorrow.

Comment on lines 2632 to 2636
### Why not allow profile overrides to override the standard library's dependencies?
[rationale-why-not-override-std-deps]: #why-not-allow-profile-overrides-to-override-the-standard-librarys-dependencies

The dependencies of the standard library are an implementation detail and ought
not be observable to the user.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps as a future extension this could be allowed on nightly.

Comment on lines 1745 to 1746
[`cargo add`][cargo-add] will add `core`, `alloc` or `std` explicitly to the
manifest if invoked with those crate names:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, interesting. Perhaps it should also enforce the nightly toolchain when trying to add it with features.

@fee1-dead
Copy link

(didn't write a summary comment in posting the review, so will do so here)

I'd like to echo ehuss in thanking the people who put in the work to make this happen. I'm happy that a much more comprehensive proposal is being developed. I share some concerns though.

That's why I was strongly recommending that this be an opt-in process, instead of automatically enabled.

Note that there's a perspective of ecosystem churn here: if all no_std crates now need to declare which specific crates they need (core or core+alloc) then it will instantly render all current #![no_std] crates unusable by build-std users. It might be wise to collect a statistic of how many crates currently on crates.io are #![no_std]. We can't always expect people to just update their crates. Some crates are essentially unmaintained with their authors uncontactable. Forks can always happen (at least for FOSS projects) but that layers additional pain to the transition.

I worry that the scope of this is too large, and that there are a many things in here that are going to be hard sells.

Well, I think there is inherent complexity in build-std. On the Zulip thread I proposed seeking team approval for an experiment to better inform the design (this was how the const traits feature evolved), where the code experimental support could be ripped out/completely refactored at any time if the team doesn't feel like the direction is correct (a sort of an initial nothing is guaranteed approach), it depends on how people feel about a similar approach here.

GitHub's UI is choking on this large PR and the large number of comments

More specifically for me, typing review comments for the diff becomes really laggy. I heard that GitHub is trying to improve stuff on that front though. I still prefer the RFC+GitHub review system for threaded discussion over issue, because I feel like the latter is more fragmented.

Comment on lines 2049 to 2055
needing to keep track of whether `std` is supported is necessary. However, it is
not obvious why keeping track of whether `alloc` and `core` are supported
individually is necessary:

Most targets will support `core`. `core` would only be set to `false` for very
experimental targets which do not support build-std at all. `alloc` would be set
to `false` for those targets that do not support allocation.
Copy link

@fee1-dead fee1-dead Jul 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A more thorough discussion on what it means for a target to have standard_library_support seems missing.

I am particularly interested in alloc because that will probably be the most contentious part. The current example in the specific section states x86_64-unknown-none as having both core and alloc support but mipsel-sony-psx as only having core support; However the rustc doc on the mipsel-sony-psx target explicitly guides users of the target to build alloc:

cargo build -Zbuild-std=core,alloc --target mipsel-sony-psx

Furthermore, it is unclear why x86_64-unknown-none is considered as support[ing] allocation. The standard procedure with build-std with that target is to bring-your-own-allocator. There needs to be a crate that specifies the global allocator.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mipsel-sony-psx was just a target I chose at random assuming it didn't have alloc support, so that's just an oversight.

I used x86_64-unknown-none as an example because it's my understanding that we ship liballoc.rlib for that target today as a pre-built artifact.

I think it's okay to permit the user to build alloc and eventually have compilation fail for lack of a global allocator. I don't think that's much different from a core-only crate not including a panic handler crate. I'm not sure if it is a responsibility of build-std/Cargo to ensure that circumstance doesn't happen.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to assume that ability to build core implies an ability to build alloc. That for me eliminates the need to separate alloc from core.

The goals section also mention an ability for people to build standard library crates that a target normally doesn't support (e.g. only using some features from std), I wonder how this ties in with a target enabling what kinds of stdlib crates downstream people can build? Maybe there should be an override option for nightly users?

Comment on lines 801 to 827
1. **Re-building the standard library with different codegen flags or profile**
([wg-cargo-std-aware#2])

- Embedded users need to optimise aggressively for size, due to the limited
space available on their target platforms, which can be achieved in Cargo by
setting `opt-level = s/z` and `panic = "abort"` in their profile. However,
these settings will not apply to the pre-built standard library
- Similarly, when deploying to known environments, use of `target-cpu` or
`target-feature` can improve the performance of code generation or allow the
use of newer hardware features than the target's baseline provides. As above,
these configuration will not apply to the pre-built standard library
- While the pre-built standard library is built to support debugging without
compromising size and performance by setting `debuginfo=1`, this isn't
ideal, and building the standard library with the dev profile would provide
a better experience

2. **Unblock stabilisation of ABI-modifying compiler flags**

- Any compiler flags which change the ABI cannot currently be stabilised as they
would immediately mismatch with the pre-built standard library
- Without an ability to rebuild the standard library using these flags, it is
impossible to use them effectively and safely if stabilised
- ABI-modifying flags are designated as target modifiers ([rfcs#3716]/[rust#136966])
and require that the same value for the flag is passed to all compilation units
- Flags which need to be set across the entire crate graph to uphold some
property (i.e. enhanced security) are also target modifiers
- For example: sanitizers, control flow integriy, `-Zfixed-x18`, etc

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: How important/negotiable are these goals if they will significantly increase the contentiousness of this proposal?

Stabilizing ABI-modifying flags or otherwise custom build options, as described here, might lead to problems regarding what guarantees, especially the libs team can provide. It's hard to know nor test that all possible combinations of build options/flags/targets work, and it seems like a particularly hard goal.

Making this available on nightly is at least more realistic. But I feel like if we wanted to ship anything to stable, it might be better to only focus on building tier-3 targets for a stable MVP.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this highlights something the RFC is missing - what we propose the project's stability guarantees ought to be for what build-std enables.

This is somewhere that I expect I might diverge from other teams, but I'm absolutely comfortable with build-std enabling users to do things that might not work or build, and for the mechanisms that enable that to be stable, as long as we are clear about what guarantees we do and do not provide.

I think if we only want to support configurations on stable that are well-tested and guaranteed-to-work then build-std will be much less useful feature for the vast majority of users.

@davidtwco
Copy link
Owner Author

davidtwco commented Jul 3, 2025

I'll reply to the inline comments one-by-one as I get around to addressing them.

This discussion has reminded me that I think another missing section we have is a proposed stability guarantees for build-std - what we should and shouldn't promise will always work. build-std will inherently let the user build configurations that haven't been tested and might not always work, so we should be clear about what we're promising.

I worry that the scope of this is too large, and that there are a many things in here that are going to be hard sells. There are a lot of breaking changes in here which I feel like people won't find acceptable. I worry that trying to post this as an RFC likely will result in at least a year of discussion before we can get people on board to approve it. I don't want to sound too negative, this is really great stuff! I'm just warning you that this is going to be very difficult.

Which parts of the scope would you suggest cutting? I'm open to dropping parts if we can. I figure that the core of build-std is "what do we rebuild and when" and I've tried to keep it focused on providing a good answer to those questions and only including things which are necessary in support of those. I didn't want to propose anything that I felt was a worse-than-it-could-be solution to those questions just to avoid tricky decisions or complexity though.

I didn't think I had too many breaking changes in the RFC, at least not to stable users. I expect that this wouldn't just work for existing users of build-std on nightly but I don't think that's especially important. I don't mind breaking anyone choosing to use nightly features, if there's a bit of churn there to get those people onto a feature that has the possibility of being stabilised then I think that's absolutely worth it. I should include a section/appendix on known breaking changes being proposed - so if there are others that you spot that I have missed, please let me know.

I expect that the approach we'll take with this is to try and propose something reasonable and achievable, and then spent a decent chunk of time asking for feedback from a growing set of project members that we invite to give feedback, so that we can make sure that their concerns are addressed and that they're on-board with what we're proposing. By the time we actually open it as an RFC, I'd want many of the affected parties to already have seen the RFC and be largely on-board with it.

My confidence is low that this will be able to support all targets out the gate. I would strongly recommend thinking about a slower, more controlled rollout (rust-lang/wg-cargo-std-aware#86).

I'm absolutely open to doing this. This isn't described in the RFC and should be, but one of my motivations for the standard_library_support field was that we could set this to false for everything by default and that would effectively enforce build-std only on nightly, where I think it would be okay if not every target worked. Per other sections of the RFC, custom targets are nightly-only too so users can't use that as a mechanism to bypass this. We could then enable build-std on stable on a per-target per-standard-library-crate basis once we're confident for any given target/crate.

I'm worried about how this is setting up an expectation that just building the standard library for all targets will "just work" for everyone. I think that is going to be extremely difficult, especially with the other goal of expecting it to match the pre-built standard library. That's why I was strongly recommending that this be an opt-in process, instead of automatically enabled.

I think build-std being as-transparent-as-possible is one of the key aspects of the RFC that I'd like to keep. I think there are aspects of build-std, like enabling features, that are relatively easy to separate and not address in this RFC. Unless we can argue that a detail can be postponed and doesn't need to be addressed now, or that an alternative is better from a technical/user experience perspective, I'd like to try and pursue as close to the ideal for build-std as we can. I don't think that's incompatible with trying to keep the scope small, I'm happy to cut as many extraneous parts as we can, but with what remains - like how-build-std is enabled - I'd like to go for a solution as close to the ideal as possible.

More concretely, I think what we're proposing in the current draft is pretty close to an opt-in, we could default to build-std = "off" and stabilise build-std = "target-modifiers" and build-std = "always" individually afterwards - perhaps I could drop build-std = "always" entirely from this RFC and only focus on the target modifier use-case, that's tempting actually. I think it's okay if these options don't "just work" when they aren't stable. You'd need to opt-in by changing to "target-modifiers" but then you're opting-in to build-std working transparently and in the way that I think is most intuitive. I expect we might want to default to "target-modifiers" because that's still a much more constrained problem where we only rebuild under very specific circumstances.

It might be that you disagree that a transparent build-std is the wrong user experience in general, not just because it's more complex to propose/implement, and if that's the case, I'd love to know more about why that is.

Matching the pre-built standard library is certainly more difficult, and it's the part of the RFC that I have the least confidence in at the moment. I think it's a worthwhile goal, but I am happy to compromise on it if we can't work out a mechanism that seems feasible to do it.

There doesn't seem to be much discussion about downloading the source. I don't think we can expect users to manually download the rust-src component. At a minimum, I think it would need to be made a required component (or just ship with rustc). This could also be a breaking change for non-rustup users. This would also be a breaking change for existing vendored users. This could also be a breaking change for users with partial mirrors of crates.io.

I hadn't considered requiring rustup users to download rust-src to be that much of an impediment. I expect that use of build-std would be rare by default for the vast majority of users, only when they toggle an option which requires build-std (i.e. a target modifier, like enabling sanitisers) would they encounter build-std. It would be more common for subsets of the community like embedded users who might want something along the lines of build-std = "always". If Cargo emitted an error and suggested the user download rust-src from rustup and then the user had to do that, it's not the ideal user experience, but I don't think it's the worst thing in the world, and could certainly be improved later.

That said, I'd be happy to write that rust-src become a required component.

While we haven't discussed the non-rustup use-case specifically in the RFC, it was my intention that by documenting the path in the sysroot that sources are expected to be found, that would be sufficient for non-rustup users (e.g. distro packages) to know how to set up their toolchain such that build-std could work. I don't think that is a breaking change for those users though: if build-std defaulted to build-std = "target-modifiers" or build-std = "off", then I don't think that anything the user could be doing today would break - they would need to be able to require that the standard library be rebuilt, which is to say, use a target modifier, and Cargo has no ability to change any target modifiers because of a lack of build-std.

I hadn't considered existing vendored users or those with partial mirrors of crates.io. I'll make sure we consider that and mention it in the RFC.

I'm a bit worried about the goal of being able to match the pre-built standard library is really high. I think that is going to be very difficult.

For example, I don't see much discussion about optimized compiler-builtins and such.

I don't see much discussion in here about compiler-rt or profiler support.

I've noted this myself in one of the comments above, these are gaps that we just need to address.

I'm unclear on how this would work with RUSTFLAGS. Cargo has no knowledge of what those are, and so if the user sets RUSTFLAGS, that seems like it could run into some significant conflicts (particularly with some of the "target modifier" flags that were previously mentioned).

I'll mention this in the RFC, but I expected that like today, Cargo would not look at RUSTFLAGS and if the user sets them in such a way that something breaks, that is their responsibility. Target modifiers are checked by the compiler so if the user used RUSTFLAGS to set a target modifier rather than setting it Cargo and so std was not rebuilt, the compiler would error rather than an incorrect artifact being produced.

GitHub's UI is choking on this large PR and the large number of comments. I can live with it for now, but I think if the intent is to post this in its entirety on the RFC repo, it could make things difficult. That was also partly why we started the repo at rust-lang/wg-cargo-std-aware, so that individual threads could be split into issues and we could discuss and come to resolution on them, and to write separate documents and work on them somewhat independently.

GitHub is struggling, but I'm not sure how better to do this - a repo runs the risk of discussions/information becoming very disjoint and hard to follow, requiring an effort like this to tie all the threads back together again. I could split the RFC up into different files in a 0000-build-std directory (following the precedent of 3606-temporary-lifetimes-in-tail-expressions and 3392-leadership-council) and hopefully a handful of relatively smaller files is something GitHub could cope with better.

Note that there's a perspective of ecosystem churn here: if all no_std crates now need to declare which specific crates they need (core or core+alloc) then it will instantly render all current #![no_std] crates unusable by build-std users. It might be wise to collect a statistic of how many crates currently on crates.io are #![no_std]. We can't always expect people to just update their crates. Some crates are essentially unmaintained with their authors uncontactable. Forks can always happen (at least for FOSS projects) but that layers additional pain to the transition.

I think this is true, there would be churn. I assumed that this churn would largely affect users already on nightly using build-std, rather than no-std crates being used on stable. I think that's fine. It's not great, sure, but if that part of the Rust ecosystem needs to update their crates so they can use a build-std that has a chance of being stabilised, I think that's fine and worth it.

It's possible there are things we've missed here for targets like x86_64-unknown-none that ship core but not std, where you could have a project on stable that uses #![no_std] and extern crate core, and this might cause problems. I'll need to think about that more and make sure we address it in the RFC.

@fee1-dead
Copy link

btw, it would be nice to use Co-Authored-By: beef <ent3rm4n@gmail.com> for my suggestions :)

@davidtwco
Copy link
Owner Author

btw, it would be nice to use Co-Authored-By: beef <ent3rm4n@gmail.com> for my suggestions :)

We'll squash all the history before this is actually proposed, but I'll make sure you're added to the acknowledgements section.

@ehuss
Copy link

ehuss commented Jul 3, 2025

Just to review my proposal of reducing scope:

  • Introduce some opt-in mechanism.

    • I would expect it to be some kind of a config.toml option. I don't have a specific proposal, but I would expect it to be something similar to the proposal in allow build-std to be configured per-target in .cargo/config.toml rust-lang/cargo#15670 with a [target] table option. I would also expect some way to specify it without specifying a target (maybe build.build-std? i don't know).
    • For the most part, this will work just like it does today.
  • Frame this RFC that it would be multiple steps, where the opt-in option could be done first, and then the remaining work can be done following that. The intent is to get something shipped that is useful as soon as possible, and to avoid getting stuck trying to hammer out all of the details at once (or to risk a difficult rollout where we spend a lot of time chasing down problems).

  • The things that I would shift out of the first step:

    • Explicit standard-library dependencies in Cargo.toml
    • Index changes
    • Resolver changes
    • Registry changes
    • crates.io changes
    • Lockfile changes
    • Patches
    • Profile changes
    • Automatic detection of when to rebuild the standard library
      • Profile change detection
      • Knowledge of target modifiers
    • Removal of rustc-dep-of-std
  • Removing the automatic recompilation would avoid some concerns:

    • Making sure that rebuilding actually works for all users on all systems for all targets. If it doesn't work, then it was the user's choice to opt-in to that, and it avoids breaking changes and regressions.
    • Avoiding the unknowns about how various rustc flags would impact the standard library. This is somewhat related to the discussion about "what are our stability guarantees?", since these configurations will not have ever been tested.
    • Give us more runway to solve problems. Shipping a stable opt-in mechanism means users can incrementally adopt this new feature, and we will learn what kinds of problems that people encounter. As opposed to trying to unload it to all users at once, and then being deluged with problems.
    • Concerns around performance issues with rebuilding the standard library, or disk space usage.
  • Add a small section that would explain that the transition from the opt-in mechanism to the explicit dependencies would work. I can't think of much to say here, other than if the config.toml option is enabled, then cargo will always rebuild std, otherwise it will use automatic detection (as currently described in the document).

  • There could be some intermediate steps if you'd like. One idea is to automatically detect if you are building for a tier-3 target, and it is not in the sysroot, to automatically build std for you. I don't know if that makes sense, since it could be surprising to the user and result in inconsistent behavior. But I imagine if there could be some things that could be stabilized in an incremental fashion to ease the rollout.

Does that make sense?

adamgemmell and others added 4 commits July 8, 2025 17:43
Removed implicit direct deps as it was unnecessary - implicit deps still
includes core & alloc as before.
The default behaviour of `compiler_builtins` can and should be improved to allow
`build-std` to match the prebuilt standard library as much as possible. The
`compiler-rt` sources should be distributed with `rust-src` (as `libunwind` is
today). By default `compiler_builtin`'s `build.rs` should make a best-effort
Copy link
Collaborator

@adamgemmell adamgemmell Jul 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this section should be removed and we have to accept that by default build-std will often perform worse that prebuilt for now.

The issue is that when a target-modifier is changed, CFLAGS (which should be advertised as being respected) needs to be changed at the same time. If it isn't then the best case scenario for the user is a big meaty linker error and the worst case is some kind of CFI not being fully applied. I'm not sure how otherwise to make the best-effort approach work.

re-builds the standard library crates which rustc then uses instead of the
pre-built standard library from the sysroot.

`-Zbuild-std` builds `std` by default. `test` is also built if `std` is being
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To clarify a minor point here, the default is dynamic. If rustc indicates that the target does not support std, then it only builds core. It uses the std field from --print=target-spec-json (added in rust-lang/rust#122305).

@davidtwco davidtwco changed the title build-std build-std: first iteration Jul 22, 2025
@davidtwco
Copy link
Owner Author

As discussed in the sync call, we've rewritten a bunch of this to present the RFC in stages following your feedback. Much of the existing feedback is rendered irrelevant and we've tried to address everything else, so re-opening this in #2 to avoid having to uncollapse GitHub threads. We're still to discuss interactions of per-pkg-target in the RFC, but that'll be added by @adamgemmell soon.

@davidtwco davidtwco closed this Jul 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants