-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
RFC: fallible-allocation #3140
base: master
Are you sure you want to change the base?
RFC: fallible-allocation #3140
Conversation
cc @rust-lang/libs |
|
||
## Making this feature accessible to Cargo | ||
If sysroot crates (`std`, `core`, `compiler_builtins`, `alloc`) are specified as a dependency in the manifest, Cargo will attempt to locate a variant of that crate which has the minimum set of requested features to build the package. | ||
Since features union as usual, having a crate with dependencies declared this way would be similar to a modern day `#![no_std]` crate: crates depending on it could still use the full `std` as usual with no issues. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't this mean that if you ever use a crate that doesn't specify default-features = false
for alloc/std you may suddenly have default-features
enabled?
I just tested this crate layout:
fn_under_default_feature
:
src.rs:
[cfg(feature = hmm)]
pub fn hmm() {}
cargo.toml:
...
[features]
default = ["hmm"]
hmm = []
If I have a crate depend on this with default-features = false
, I correctly can't use hmm
, but if I add this crate to my downstream crate:
cargo.toml:
[dependencies]
fn_under_default = { path = "../fn_under_default" } # note no `default-features`
In my downstream crate, I can suddenly import and use the hmm
fn
Does this mean that someone trying to use alloc = { default-features = false }
can accidentally add a crate that doesn't specify turning off the default features and the compiler will longer protect them?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the [no_std]
case you suddenly will try to link std in (I think) and fail to compile (at least in the embedded case, I think), but in this case, you suddenly can access api's right, which you get no warning for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that is how default features work. There is currently no way to tell cargo "turn this off and keep it off and i don't care if that makes the build fail i want it to stay off no matter what".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. You'll note that this is listed under potential future things to address. This behavior is actually beneficial, because it allow crates to state their compatibility with a restricted API while still being usable in more common environments.
If you're considering using this in an environment where you want to be sure the feature is off in the final product, you could consider removing the full std
or full alloc
from the sysroot, or using .cargo/config.toml
and rustflags
to set a custom sysroot containing only restricted libraries.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Noting it seems reasonable! It's also possible that the kernel/other projects use cargo-guopy to check their deps for unwanted properties
The general concept of If crates start declaring dependencies on I think, instead, we'd need to have a syntax to say "default minus |
The way this is solved in other crates is to move everything behind a feature and make that default on. You can then in the future subset that feature as necessary, and make it activate those new sub-features. |
## Testing | ||
In CI, `std` and `alloc`'s ability to build without the `infallible_allocation` feature only needs to be tested on a single platform (likely `x86_64-unknown-linux-gnu`) in order to prevent developers accidentally forgetting to tag new functions with the appropriate feature gate. | ||
|
||
When cutting release, all Tier-2 platforms should perform a build with `infallible_allocation` disabled to ensure nothing architecture-specific slipped through (though this should be rare). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As std
grows more feature flags this turns into an O(n!)
operation, it might be worth thinking about how to mitigate that before adding feature support at all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The "portability" lint solves this. Features like this which just add items are closed to being able to turn into separate upstream crates (as proposed in the alternatives) which also solves the problem. splitting crates should be thought of as a special case of the portability lint in general, just as https://en.wikipedia.org/wiki/Horn-satisfiability is an easier special case of https://en.wikipedia.org/wiki/Boolean_satisfiability_problem
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was unaware of the portability lint. After reading the portability lint's RFC, it seems like exactly what we'd want here. That would be superior to the extra builds I'm proposing, but as far as I can tell it's not implemented yet. Since the RFC is merged, hopefully we'll get it before an O(n!)
feature explosion catches us.
@joshtriplett it would be useful to reform the concept of default features to mean "give me the default features as of x.y.z". That would solve the problem entirely, and I think corresponds to the way people actually use semver most of the time which is "I tested with x.y.z so give me that or any other compatible version". |
That is much less forward compatible. Removing a specific feature from the default for your own crate's usage is much better. Also, a way to "forbid" a feature, at least from a binary, would be nice but not strictly necessary. |
@Lokathor What is? How so? |
Saying "default features for x.y.z" means developers can't add a new on by default feature and you get it automatically. Which would, for example, completely destroy the entire effort this RFC is trying to attempt. Saying "default features but minus this one feature i know i don't want" lets you remove a specific offending feature without upsetting the ability for new on by default features in the library. |
Annotation is manual, so we don't need a suppresion mechanism.
@Lokathor Let my clarify that when I say "default features for x.y.z", i mean not the literal feature set but the interface implied by that. so adding new features does work. As @Nemo157 says
My thing would be the equivalent of turning default = [ "foo, "bar" ] into default_x_z_y = [ "foo, "bar" ]
default = [ "default_x_z_y" ] and then later versions can define Stepping back a bit, feature sets form a partial order, and when packages are compatible, there should be a partial order homomorphism from the old package's feature sets to the new ones. This accounts for all the scenarios we were imagining. The trick is simply to make defining the partial order relationship as easy as possible because users won't want to think about the math. Stepping back again, none of these issues are unique to the standard library and the RFC at hand; the same problems can block crates.io. I sometimes feel holding Cargo to a higher standard for stdlib crates is holding us back, we should other make Cargo better for everyone, or not let this stuff get in the way of making Cargo and the standard library rely on each other more. |
I feel that this should be implemented as a lint rather than messing with Cargo features. Fundamentally this is about enforcing a form of code style ("don't use infallible allocation") rather than working around a missing OS feature ("my target doesn't have a memory allocator"). |
Is it? I can imagine, over time, some stdlib features getting dual support for alloc and no-alloc, where they still work without allocations but perhaps not as efficiently. |
sorting would be a good example --- some algorithms need extra memory (e.g. merge sort), others can work in-place (e.g. quick sort). |
We already have a split for alloc/no-alloc: everything that requires allocation is in |
No, I'm talking about things that can work with a split at an implementation level. This RFC is for libstd, not just core. |
It's unclear to me which parts of std would actually be usable without To be clear, I don't want to get into specifics in this thread, but it would be useful if RFC contained some kind of a table which, for each |
I don't think a ton of If you think it would be useful to go through and annotate I can do that. I would have to do that eventually anyways to actually implement the feature split, so I can try doing the feature split and add to the RFC a summary of what would land on what side of the split, and how easy it would be to move things to out from under the feature. |
If you start returning BadAlloc from something that previously could only fail on a precondition violation, then you have introduced a breaking change to the API contract: previously, callers could assume that if the preconditions held, then the function succeeded. Callers that use See related discussion around rust-lang/rust#84612, e.g. rust-lang/rust#84612 (comment). A possible counterargument is that (on most OSes?), most syscalls that take strings are fallible, so at least for the I suppose one option would be to only return BadAlloc when |
I believe that is indeed the proposal |
For functions returning I agree that it is not necessarily the case that already returning a However, I think this should be a separate discussion, not for this RFC. This RFC only adds the distinction between |
I've nominated this RFC for discussion in the next @rust-lang/libs-api meeting. Here are my notes and questions from my review of the RFC / comments so far:
How do we want to resolve the enabled by default feature forward compatibility issue? Proposals:
Do the latter two solutions even solve the issue around not being able to add new default features? Why can't you still use |
I agree, the question of the precise interfaces is separable. But I think there's a question to be answered now, too--what's the process for marking APIs as in the safe-for-fallible set? Is it opt-in or opt-out? I believe currently whether an API in the
And they must preserve this behavior for all time. This may not always be desirable, even if a function does not currently allocate. Similar to |
I am thinking of opening a Cargo RFC about that problem. I figure having two separate technical discussions is better since the deficiency with Cargo features is in no way specific to this RFC or the standard library. The final policy decision of course will depend on both, but I still think separate threads will probably be more manageable. |
@maurer Out of curiosity, how well does the portability lint address your use case? I do feel like feature flagging is the better option here, but it's probably worth comparing the two. |
Co-authored-by: Jane Lusby <[email protected]>
@Manishearth I don't see why they would be mutually exclusive.
It thus seems clear to me that one wants both. The real alternative is |
@Ericson2314 Yeah, that's fair! To be clear; I wasn't 100% sure on how the portability lint fits in here, but also I hadn't seen it addressed in the thread so I asked. That's useful context, thanks! |
We discussed this in today's @rust-lang/libs-api meeting. We didn't have any objections in principle to the concept of this. Our biggest concern was the stability guarantees around these feature flags; we're very concerned about ending up with unexpected breakage when trying to add or modify feature flags over time. If we can address that, such as by ensuring that this can only be used on a nightly toolchain, then we'd be in favor of enabling experimentation here. We need to see a plan for how that could potentially work. (There are several such proposals in this thread; we just need to see a concrete proposal to use one of them that's capable of meeting our stability requirements, and that for now would keep this exclusive to nightly.) |
|
The issue is that build-std may be stabilized while a big feature like fallible allocation still needs time to bake as unstable. |
Right, it's not a permanent solution, just a stop-gap. |
Hmm, I'm actually hitting a similar issue in In that situation, |
Okay, possibly silly comment: why don't we make a general syntax to say "don't enable this specific feature, even if it's part of default"? It's always felt like a big hole to me that to opt out of even one default feature, you have to opt out of all of them, absent the library being specifically designed to deal with the situation. I know you can always work around that by manually enabling them, but then you bump into the same problem we do here. |
Not a silly comment. I think this is definitely a possible option we could go for and should be considered as an option in the RFC/Plan for how we can resolve the forward compatibility concern around |
how about |
Opting out is no solution. Now if someone else adds a regular optional feature, well that's getting dragged in to. And say that new feature depend on the one you opted out of! |
opting out of features could be transitive, where if feature |
@programmerjake that would mean it becomes a breaking change to add dependencies between existing features, because that means opt-outs can turn off more features than before. Thinking about comparability is hard enough without allowing negative reasoning. |
but adding dependencies is already a breaking change if you have feature a which is changed to depend on feature b and you want feature a but require feature b to not be enabled (e.g. feature b could depend on std on a no_std target). making it so opting out of feature b automatically disables feature a isn't that different -- you would still not be able to use feature a without feature b after the breaking change. |
@programmerjake It's really important to distinguish between:
Only the latter is a real breaking change. The former is still unfortunate, but allows the solver to do it's job keeping the user's build working. Today it is impossible to force off a feature in dependency specifications, and I even believe it is disallowed in the workspace root, so you would just get a With your thing, however, would loose features you always had before, which would trigger a build failure, the second case. |
I disagree, both are breaking changes: so are you saying cargo will automatically move to an older version of the package if it fails to find a solution with the newer version? that's news to me. If cargo doesn't automatically move to an older version, it's still a breaking change since if cargo can't solve the features it will fail to build, breaking previously working code -- just failing before running rustc instead of while running rustc. If cargo automatically moves to an older version, I'd still consider it a breaking change since cargo silently ignores the new version, which may fix critical bugs, even if the version number is compatible, making it very user-unfriendly.
no, if you declare the features you depend on then the solver will error after that broken crate made a breaking change. they only get silently disabled if you were depending on the feature being enabled by default, rather than explicitly naming it or some feature that transitively depends on it. In any case, the problem is caused by the crate making the breaking change but not correctly reflecting that in the version number. |
I am going to finish writing my counter-proposal soon and I think it would be better to discuss that there. It is rather off topic here. |
My proposal is up: #3146 |
Move infallibly allocating
alloc
andstd
library functions behind a feature, as an extension/stabilization ofno_global_oom_handler
.This RFC is specifically not looking to add any new fallible interfaces; those can be handled separately.
Rendered
CCing known interested parties:
@Ericson2314 @joshtriplett @Manishearth