|
| 1 | +- Feature Name: `liballoc` |
| 2 | +- Start Date: 2018-06-14 |
| 3 | +- RFC PR: [rust-lang/rfcs#2480](https://github.com/rust-lang/rfcs/pull/2480) |
| 4 | +- Rust Issue: [rust-lang/rust#27783](https://github.com/rust-lang/rust/issues/27783) |
| 5 | + |
| 6 | +# Summary |
| 7 | +[summary]: #summary |
| 8 | + |
| 9 | +Stabilize the `alloc` crate. |
| 10 | + |
| 11 | +This crate provides the subset of the standard library’s functionality that requires |
| 12 | +a global allocator (unlike the `core` crate) and an allocation error handler, |
| 13 | +but not other operating system capabilities (unlike the `std` crate). |
| 14 | + |
| 15 | + |
| 16 | +# Motivation |
| 17 | +[motivation]: #motivation |
| 18 | + |
| 19 | +## Background: `no_std` |
| 20 | + |
| 21 | +In some environments the `std` crate is not available: |
| 22 | +micro-controllers that don’t have an operating system at all, kernel-space code, etc. |
| 23 | +The `#![no_std]` attribute allows a crate to not link to `std` implicitly, |
| 24 | +using `core` instead with only the subset of functionality that doesn’t have a runtime dependency. |
| 25 | + |
| 26 | +## `no_std` with an allocator |
| 27 | + |
| 28 | +The `core` crate does not assume even the presence of heap memory, |
| 29 | +and so it excludes standard library types like `Vec<T>`. |
| 30 | +However some environments do have a heap memory allocator |
| 31 | +(possibly as `malloc` and `free` C functions), |
| 32 | +even if they don’t have files or threads |
| 33 | +or something that could be called an operating system or kernel. |
| 34 | +Or one could be defined [in a Rust library][wee-alloc] |
| 35 | +ultimately backed by fixed-size static byte array. |
| 36 | + |
| 37 | +An intermediate subset of the standard library smaller than “all of `std`” |
| 38 | +but larger than “only `core`” can serve such environments. |
| 39 | + |
| 40 | +[wee-alloc]: https://github.com/rustwasm/wee_alloc |
| 41 | + |
| 42 | +## Libraries |
| 43 | + |
| 44 | +In 2018 there is a [coordinated push] |
| 45 | +toward making `no_std` application compatible with Stable Rust. |
| 46 | +As of this writing not all of that work is completed yet. |
| 47 | +For example, [`#[panic_implementation]`][panic-impl] is required for `no_std` but still unstable. |
| 48 | +So it may seem that this RFC does not unlock anything new, |
| 49 | +as `no_std` application still need to be on Nigthly anyway. |
| 50 | + |
| 51 | +[coordinated push]: https://github.com/rust-lang-nursery/embedded-wg/issues/42 |
| 52 | +[panic-impl]: https://github.com/rust-lang/rust/issues/44489 |
| 53 | + |
| 54 | +The immediate impact can be found in the library ecosystem. |
| 55 | +Many general-purpose libraries today are compatible with Stable Rust |
| 56 | +and also have potential users who ask for them to be compatible with `no_std` environments. |
| 57 | + |
| 58 | +For a library that is fundamentally about using for example TCP sockets or threads, |
| 59 | +this may not be possible. |
| 60 | + |
| 61 | +For a library that happens to only use parts of `std` that are also in `core` |
| 62 | +(and are willing to commit to keep doing so), this is relatively easy: |
| 63 | +add `#![no_std]` to the crate, and change `std::` in paths to `core::`. |
| 64 | + |
| 65 | +And here again, there is the intermediate case of a library that needs `Vec<T>` |
| 66 | +or something else that involves heap memory, but not other parts of `std` that are not in `core`. |
| 67 | +Today, in order to not lose compatibility with Stable, |
| 68 | +such a library needs to make compatibility with `no_std` an opt-in feature flag: |
| 69 | + |
| 70 | +```rust |
| 71 | +#![no_std] |
| 72 | + |
| 73 | +#[cfg(feature = "no_std")] extern crate alloc; |
| 74 | +#[cfg(not(feature = "no_std"))] extern crate std as alloc; |
| 75 | + |
| 76 | +use alloc::vec::Vec; |
| 77 | +``` |
| 78 | + |
| 79 | +But publishing a library that uses unstable features, even optionally, |
| 80 | +comes with the expectation that it will be promptly updated whenever those features change. |
| 81 | +Some maintainers are not willing to commit to this. |
| 82 | + |
| 83 | +With this RFC, the library’s code can be simplified to: |
| 84 | + |
| 85 | +```rust |
| 86 | +#![no_std] |
| 87 | + |
| 88 | +extern crate alloc; |
| 89 | + |
| 90 | +use alloc::vec::Vec; |
| 91 | +``` |
| 92 | + |
| 93 | +… and perhaps more importantly, |
| 94 | +maintainers can rely on the stability promise made by the Rust project. |
| 95 | + |
| 96 | + |
| 97 | +# Guide-level explanation |
| 98 | +[guide-level-explanation]: #guide-level-explanation |
| 99 | + |
| 100 | +## For libraries |
| 101 | + |
| 102 | +When using `#![no_std]` in a crate, that crate does not implicitly depend on `std` |
| 103 | +but depends on `core` instead. For example: |
| 104 | + |
| 105 | +```diff |
| 106 | +-use std::cell::RefCell; |
| 107 | ++use core::cell::RefCell; |
| 108 | +``` |
| 109 | + |
| 110 | +APIs that require a memory allocator are not available in `core`. |
| 111 | +In order to use them, `no_std` Rust code must explicitly depend on the `alloc` crate: |
| 112 | + |
| 113 | +```rust |
| 114 | +#[macro_use] extern crate alloc; |
| 115 | + |
| 116 | +use core::cell::RefCell; |
| 117 | +use alloc::rc::Rc; |
| 118 | +``` |
| 119 | + |
| 120 | +Note: `#[macro_use]` imports the [`vec!`] and [`format!`] macros. |
| 121 | + |
| 122 | +[`vec!`]: https://doc.rust-lang.org/alloc/macro.vec.html |
| 123 | +[`format!`]: https://doc.rust-lang.org/alloc/macro.format.html |
| 124 | + |
| 125 | +Like `std` and `core`, this dependency does not need to be declared in `Cargo.toml` |
| 126 | +since `alloc` is part of the standard library and distributed with Rust. |
| 127 | + |
| 128 | +The implicit prelude (set of items that are automatically in scope) for `#![no_std]` crates |
| 129 | +does not assume the presence of the `alloc` crate, unlike the default prelude. |
| 130 | +So such crates may need to import either that prelude or specific items explicitly. |
| 131 | +For example: |
| 132 | + |
| 133 | +```rust |
| 134 | +use alloc::prelude::*; |
| 135 | + |
| 136 | +// Or |
| 137 | + |
| 138 | +use alloc::string::ToString; |
| 139 | +use alloc::vec::Vec; |
| 140 | +``` |
| 141 | + |
| 142 | +## For programs¹ |
| 143 | + |
| 144 | +[¹] … and other roots of a dependency graph, such as `staticlib`s. |
| 145 | + |
| 146 | +Compared to `core`, the `alloc` crate makes two additional requirements: |
| 147 | + |
| 148 | +* A global heap memory allocator. |
| 149 | + |
| 150 | +* An allocation error handler (that is not allowed to return). |
| 151 | + This is called for example by `Vec::push`, whose own API is infallible, |
| 152 | + when the allocator fails to allocate memory. |
| 153 | + |
| 154 | +`std` provides both of these. So as long as it is present in the dependency graph, |
| 155 | +nothing else is required even if some crates of the graph use `alloc` without `std`. |
| 156 | + |
| 157 | +If `std` is not present they need to be defined explicitly, |
| 158 | +somewhere in the dependency graph (not necessarily in the root crate). |
| 159 | + |
| 160 | +* [The `#[global_allocator]` attribute][global_allocator], on a `static` item |
| 161 | + of a type that implements the `GlobalAlloc` trait, |
| 162 | + defines the global allocator. It is stable in Rust 1.28. |
| 163 | + |
| 164 | +* [Tracking issue #51540] propose the `#[alloc_error_handler]` attribute |
| 165 | + for a function with signature `fn foo(_: Layout) -> !`. |
| 166 | + As of this writing this attribute is implemented but unstable. |
| 167 | + |
| 168 | +[global_allocator]: https://doc.rust-lang.org/nightly/std/alloc/#the-global_allocator-attribute |
| 169 | +[Tracking issue #51540]: https://github.com/rust-lang/rust/issues/51540 |
| 170 | + |
| 171 | + |
| 172 | +# Reference-level explanation |
| 173 | +[reference-level-explanation]: #reference-level-explanation |
| 174 | + |
| 175 | +The `alloc` crate already exists (marked unstable), |
| 176 | +and every public API in it is already available in `std`. |
| 177 | + |
| 178 | +Except for the `alloc::prelude` module, since [PR #51569] the module structure is a subset |
| 179 | +of that of `std`: every path that starts with `alloc::` is still valid and point to the same item |
| 180 | +after replacing that prefix with `std::` (assuming both crates are available). |
| 181 | + |
| 182 | +The concrete changes proposed by this RFC are: |
| 183 | + |
| 184 | +* Stabilize `extern crate alloc;` |
| 185 | + (that is, change `#![unstable]` to `#![stable]` near the top of `src/liballoc/lib.rs`). |
| 186 | + |
| 187 | +* Stabilize the `alloc::prelude` module and its contents |
| 188 | + (which is only re-exports of items that are themselves already stable). |
| 189 | + |
| 190 | +* Stabilize the fact that the crate makes no more and no less than |
| 191 | + the two requirements/assumptions of a global allocator and an allocation error handler |
| 192 | + being provided for it, as described above. |
| 193 | + |
| 194 | + The exact mechanism for [providing the allocation error handler][Tracking issue #51540] |
| 195 | + is not stabilized by this RFC. |
| 196 | + |
| 197 | + In particular, this RFC proposes that the presence of a source of randomness |
| 198 | + is *not* a requirement that the `alloc` crate can make. |
| 199 | + This is contrary to what [PR #51846] proposed, |
| 200 | + and means that `std::collections::hash_map::RandomState` cannot be moved into `alloc`. |
| 201 | + |
| 202 | +[Tracking issue #27783] tracks “the `std` facade”: |
| 203 | +crates whose contents are re-exported in `std` but also exist separately. |
| 204 | +Other such crates have already been moved, merged, or stabilized, |
| 205 | +such that `alloc` is the only remaining unstable one. |
| 206 | +Therefore #27783 can serve as the tracking issue for this RFC |
| 207 | +and can be closed once it is implemented. |
| 208 | + |
| 209 | +[PR #51569]: https://github.com/rust-lang/rust/pull/51569 |
| 210 | +[PR #51846]: https://github.com/rust-lang/rust/pull/51846 |
| 211 | +[Tracking issue #27783]: https://github.com/rust-lang/rust/issues/27783 |
| 212 | + |
| 213 | + |
| 214 | +The structure of the standard library is therefore: |
| 215 | + |
| 216 | +* `core`: has (almost) no runtime dependency, every Rust crate is expected to depend on this. |
| 217 | +* `alloc`: requires a global memory allocator, |
| 218 | + either specified through the `#[global_allocator]` attribute |
| 219 | + or provided by the `std` crate. |
| 220 | +* `std`: re-exports the contents of `core` and `alloc` |
| 221 | + so that non-`no_std` crate do not need care about what’s in what crate between these three. |
| 222 | + Depends on various operating system features such as files, threads, etc. |
| 223 | +* `proc-macro`: depends on parts of the compiler, typically only used at build-time |
| 224 | + (in procedural macro crates or Cargo build scripts). |
| 225 | + |
| 226 | + |
| 227 | + |
| 228 | +# Drawbacks |
| 229 | +[drawbacks]: #drawbacks |
| 230 | + |
| 231 | +[Tracking issue #27783] is the tracking issue for the `alloc` crate and, historically, some other crates. |
| 232 | +Although I could not find much discussion of that, I believe it has been kept unstable so far |
| 233 | +because of uncertainty of what the eventual desired crate structure |
| 234 | +for the standard library is, given infinite time and resources. |
| 235 | + |
| 236 | +In particular, should we have a single crate with some mechanism for selectively disabling |
| 237 | +or enabling some of the crate’s components, depending on which runtime dependencies |
| 238 | +are available in targeted environments? |
| 239 | +In that world, the `no_std` attribute and standard library crates other than `std` |
| 240 | +would be unnecessary. |
| 241 | + |
| 242 | +By stabilizing the `alloc` crate, we commit to having it − and its public API − exist “forever”. |
| 243 | + |
| 244 | + |
| 245 | +# Rationale and alternatives |
| 246 | +[alternatives]: #alternatives |
| 247 | + |
| 248 | +## Single-crate standard library |
| 249 | + |
| 250 | +The `core` and the `no_std` attribute are already stable, |
| 251 | +so in a sense it’s already too late for the “pure” version of the vision described above |
| 252 | +where `std` really is the only standard library crate that exists. |
| 253 | + |
| 254 | +It may still be [desirable] to regroup the standard library into one crate, |
| 255 | +and it is probably still possible. |
| 256 | +The `core` crate could be replaced with a set of `pub use` reexport |
| 257 | +to maintain compatibility with existing users. |
| 258 | +Whatever the eventual status for `core` is, |
| 259 | +we can do the same for `alloc`. |
| 260 | +[PR #51569] mentioned above also hopes to make this easier. |
| 261 | + |
| 262 | +While we want to leave the possibility open for it, |
| 263 | +at the time of this writing there are no concrete plans |
| 264 | +for implementing such a standard library crates unification any time soon. |
| 265 | +So the only alternative to this RFC seems to be |
| 266 | +leaving heap allocation for `no_std` in unstable limbo for the foreseeable future. |
| 267 | + |
| 268 | +[desirable]: https://aturon.github.io/2018/02/06/portability-vision/#the-vision |
| 269 | + |
| 270 | +## Require randomness |
| 271 | + |
| 272 | +[PR #51569] proposed adding a source of randomness to the other requirements |
| 273 | +made by the `alloc` crate. |
| 274 | +This would allow moving `std::collections::hash_map::RandomState`, |
| 275 | +and therefore `HashMap` (which has `RandomState` as a default type parameter), |
| 276 | +into `alloc`. |
| 277 | + |
| 278 | +This RFC chooses not to do this because it would make it difficult to use for example `Vec<T>` |
| 279 | +in environments where a source of randomness is not easily available. |
| 280 | + |
| 281 | +I hope that the language will eventually make it possible to have `HashMap` in `alloc` |
| 282 | +without a default hasher type parameter, and have the same type in `std` with its current default. |
| 283 | + |
| 284 | +Although I am not necessarily in favor |
| 285 | +of continuing the increase of the number of crates in the standard library, |
| 286 | +another solution for `HashMap` in `no_std` might be another intermediate crate |
| 287 | +that depends on `alloc` and adds the randomness source requirement. |
| 288 | + |
| 289 | +Additionally, with this RFC it should be possible to make https://crates.io/crates/hashmap_core |
| 290 | +compatible with Stable Rust. |
| 291 | +The downside of that crate is that although based on a copy of the same code, |
| 292 | +it is a different type incompatible in the type system with `std::collections::HashMap`. |
| 293 | + |
| 294 | + |
| 295 | +# Prior art |
| 296 | +[prior-art]: #prior-art |
| 297 | + |
| 298 | +I am not aware of a mechanism similar to `no_std` in another programming language. |
| 299 | + |
| 300 | +[Newlib] is a C library for “embedded” systems that typically don’t have an operating system. |
| 301 | +It does provide a memory allocator through `malloc` and related functions, unconditionally. |
| 302 | + |
| 303 | +[Newlib]: https://sourceware.org/newlib/ |
| 304 | + |
| 305 | + |
| 306 | +# Unresolved questions |
| 307 | +[unresolved]: #unresolved-questions |
| 308 | + |
| 309 | +* Did I miss something in [PR #51569] that makes `alloc` not a subset of `std`? |
| 310 | + A double-check from someone else would be appreciated. |
| 311 | + |
| 312 | +* Should the crate be renamed before stabilization? |
| 313 | + It doesn’t have exclusivity for memory-allocation-related APIs, |
| 314 | + since the `core::alloc` module exists. |
| 315 | + What really characterizes it is the assumption that a global allocator is available. |
| 316 | + The name `global_alloc` was proposed. |
| 317 | + (Although the crate doesn’t only contain the global allocator itself.) |
| 318 | + |
| 319 | +* ~Should the `alloc::prelude` module be moved to `alloc::prelude::v1`? |
| 320 | + This would make the `alloc` module structure a subset of `std` without exception. |
| 321 | + However, since this prelude is not inserted automatically, |
| 322 | + it is less likely that we’ll ever have a second version of it. |
| 323 | + In that sense it is closer to `std::io::prelude` than `std::prelude::v1`.~ |
| 324 | + Done in [PR #58933]. |
| 325 | + |
| 326 | +* In addition to being a subset of `std`, should the `alloc` crate (by itself) |
| 327 | + be a super-set of `core`? That is, should it reexport everything that is defined in `core`? |
| 328 | + See [PR #58175] which proposes reexporting `core::sync::atomic` in `alloc::sync`. |
| 329 | + |
| 330 | +[PR #58933]: https://github.com/rust-lang/rust/pull/58933 |
| 331 | +[PR #58175]: https://github.com/rust-lang/rust/pull/58175 |
0 commit comments