|
| 1 | +- Feature Name: `concrete-nonzero-types` |
| 2 | +- Start Date: 2018-01-21 |
| 3 | +- RFC PR: [rust-lang/rfcs#2307](https://github.com/rust-lang/rfcs/pull/2307) |
| 4 | +- Rust Issue: [rust-lang/rust#49137](https://github.com/rust-lang/rust/issues/49137) |
| 5 | + |
| 6 | +# Summary |
| 7 | +[summary]: #summary |
| 8 | + |
| 9 | +Add `std::num::NonZeroU32` and eleven other concrete types (one for each primitive integer type) |
| 10 | +to replace and deprecate `core::nonzero::NonZero<T>`. |
| 11 | +(Non-zero/non-null raw pointers are available through |
| 12 | +[`std::ptr::NonNull<U>`](https://doc.rust-lang.org/nightly/std/ptr/struct.NonNull.html).) |
| 13 | + |
| 14 | +# Background |
| 15 | +[background]: #background |
| 16 | + |
| 17 | +The `&T` and `&mut T` types are represented in memory as pointers, |
| 18 | +and the type system ensures that they’re always valid. |
| 19 | +In particular, they can never be NULL. |
| 20 | +Since at least 2013, rustc has taken advantage of that fact to optimize the memory representation |
| 21 | +of `Option<&T>` and `Option<&mut T>` to be the same as `&T` and `&mut T`, |
| 22 | +with the forbidden NULL value indicating `Option::None`. |
| 23 | + |
| 24 | +Later (still before Rust 1.0), |
| 25 | +a `core::nonzero::NonZero<T>` generic wrapper type was added to extend this optimization |
| 26 | +to raw pointers (as used in types like `Box<T>` or `Vec<T>`) and integers, |
| 27 | +encoding in the type system that they can not be null/zero. |
| 28 | +Its API today is: |
| 29 | + |
| 30 | +```rust |
| 31 | +#[lang = "non_zero"] |
| 32 | +#[unstable] |
| 33 | +pub struct NonZero<T: Zeroable>(T); |
| 34 | + |
| 35 | +#[unstable] |
| 36 | +impl<T: Zeroable> NonZero<T> { |
| 37 | + pub const unsafe fn new_unchecked(x: T) -> Self { NonZero(x) } |
| 38 | + pub fn new(x: T) -> Option<Self> { if x.is_zero() { None } else { Some(NonZero(x)) }} |
| 39 | + pub fn get(self) -> T { self.0 } |
| 40 | +} |
| 41 | + |
| 42 | +#[unstable] |
| 43 | +pub unsafe trait Zeroable { |
| 44 | + fn is_zero(&self) -> bool; |
| 45 | +} |
| 46 | + |
| 47 | +impl Zeroable for /* {{i,u}{8, 16, 32, 64, 128, size}, *{const,mut} T where T: ?Sized} */ |
| 48 | +``` |
| 49 | + |
| 50 | +The tracking issue for these unstable APIs is |
| 51 | +[rust#27730](https://github.com/rust-lang/rust/issues/27730). |
| 52 | + |
| 53 | +[`std::ptr::NonNull`](https://doc.rust-lang.org/nightly/std/ptr/struct.NonNull.html) |
| 54 | +was stabilized in [in Rust 1.25](https://github.com/rust-lang/rust/pull/46952), |
| 55 | +wrapping `NonZero` further for raw pointers and adding pointer-specific APIs. |
| 56 | + |
| 57 | +# Motivation |
| 58 | +[motivation]: #motivation |
| 59 | + |
| 60 | +With `NonNull` covering pointers, the remaining use cases for `NonZero` are integers. |
| 61 | + |
| 62 | +One problem of the current API is that |
| 63 | +it is unclear what happens or what *should* happen to `NonZero<T>` or `Option<NonZero<T>>` |
| 64 | +when `T` is some type other than a raw pointer or a primitive integer. |
| 65 | +In particular, crates outside of `std` can implement `Zeroable` for their abitrary types |
| 66 | +since it is a public trait. |
| 67 | + |
| 68 | +To avoid this question entirely, |
| 69 | +this RFC proposes replacing the generic type and trait with twelve concrete types in `std::num`, |
| 70 | +one for each primitive integer type. |
| 71 | +This is similar to the existing atomic integer types like `std::sync::atomic::AtomicU32`. |
| 72 | + |
| 73 | +# Guide-level explanation |
| 74 | +[guide-level-explanation]: #guide-level-explanation |
| 75 | + |
| 76 | +When an integer value can never be zero because of the way an algorithm works, |
| 77 | +this fact can be encoded in the type system |
| 78 | +by using for example the `NonZeroU32` type instead of `u32`. |
| 79 | + |
| 80 | +This enables code recieving such a value to safely make some assuptions, |
| 81 | +for example that dividing by this value will not cause a `attempt to divide by zero` panic. |
| 82 | +This may also enable the compiler to make some memory optimizations, |
| 83 | +for example `Option<NonZeroU32>` might take no more space than `u32` |
| 84 | +(with `None` represented as zero). |
| 85 | + |
| 86 | +# Reference-level explanation |
| 87 | +[reference-level-explanation]: #reference-level-explanation |
| 88 | + |
| 89 | +A new private `macro_rules!` macro is defined and used in `core::num` that expands to |
| 90 | +twelve sets of items like below, one for each of: |
| 91 | + |
| 92 | +* `u8` |
| 93 | +* `u16` |
| 94 | +* `u32` |
| 95 | +* `u64` |
| 96 | +* `u128` |
| 97 | +* `usize` |
| 98 | +* `i8` |
| 99 | +* `i16` |
| 100 | +* `i32` |
| 101 | +* `i64` |
| 102 | +* `i128` |
| 103 | +* `isize` |
| 104 | + |
| 105 | +These types are also re-exported in `std::num`. |
| 106 | + |
| 107 | +```rust |
| 108 | +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] |
| 109 | +pub struct NonZeroU32(NonZero<u32>); |
| 110 | + |
| 111 | +impl NonZeroU32 { |
| 112 | + pub const unsafe fn new_unchecked(n: u32) -> Self { Self(NonZero(n)) } |
| 113 | + pub fn new(n: u32) -> Option<Self> { if n == 0 { None } else { Some(Self(NonZero(n))) }} |
| 114 | + pub fn get(self) -> u32 { self.0.0 } |
| 115 | +} |
| 116 | + |
| 117 | +impl fmt::Debug for NonZeroU32 { |
| 118 | + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| 119 | + fmt::Debug::fmt(&self.get(), f) |
| 120 | + } |
| 121 | +} |
| 122 | + |
| 123 | +// Similar impls for Display, Binary, Octal, LowerHex, and UpperHex |
| 124 | +``` |
| 125 | + |
| 126 | +Additionally, the `core::nonzero` module and its contents (`NonZero` and `Zeroable`) |
| 127 | +are deprecated with a warning message that suggests using `ptr::NonNull` or `num::NonZero*` instead. |
| 128 | + |
| 129 | +A couple release cycles later, the module is made private to libcore and reduced to: |
| 130 | + |
| 131 | +```rust |
| 132 | +/// Implementation detail of `ptr::NonNull` and `num::NonZero*` |
| 133 | +#[lang = "non_zero"] |
| 134 | +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] |
| 135 | +pub(crate) struct NonZero(pub(crate) T); |
| 136 | + |
| 137 | +impl<T: CoerceUnsized<U>> CoerceUnsized<NonZero<U>> for NonZero<T> {} |
| 138 | +``` |
| 139 | + |
| 140 | +The memory layout of `Option<&T>` is a |
| 141 | +[documented](https://doc.rust-lang.org/nomicon/other-reprs.html#reprc) |
| 142 | +guarantee of the Rust language. |
| 143 | +This RFC does **not** propose extending this guarantee to these new types. |
| 144 | +For example, `size_of::<Option<NonZeroU32>>() == size_of::<NonZeroU32>()` may or may not be true. |
| 145 | +It happens to be in current rustc, |
| 146 | +but an alternative Rust implementation could define `num::NonZero*` purely as library types. |
| 147 | + |
| 148 | +# Drawbacks |
| 149 | +[drawbacks]: #drawbacks |
| 150 | + |
| 151 | +This adds to the ever-expanding API surface of the standard library. |
| 152 | + |
| 153 | +# Rationale and alternatives |
| 154 | +[alternatives]: #alternatives |
| 155 | + |
| 156 | +* Memory layout optimization for non-zero integers mostly exist in rustc today |
| 157 | + because their implementation is very close (or the same) as for non-null pointers. |
| 158 | + But maybe they’re not useful enough to justify any dedicated public API. |
| 159 | + `core::nonzero` could be deprecated and made private without adding `num::NonZero*`, |
| 160 | + with only `ptr::NonNull` exposing such functionality. |
| 161 | + |
| 162 | +* On the other hand, |
| 163 | + maybe zero is “less special” for integers than NULL is for pointers. |
| 164 | + Maybe instead of `num::NonZero*` we should consider some other feature |
| 165 | + to enable creating integer wrapper types that restrict values to an arbitrary sub-range |
| 166 | + (making this known to the compiler for memory layout optimizations), |
| 167 | + similar to how [PR #45225](https://github.com/rust-lang/rust/pull/45225) |
| 168 | + restricts the primitive type `char` to `0 ..= 0x10FFFF`. |
| 169 | + Making entire bits available unlocks more potential future optimizations than a single value. |
| 170 | + |
| 171 | + However no design for such a feature has been proposed, whereas `NonZero` is already implemented. |
| 172 | + The author’s position is that `num::NonZero*` should be added |
| 173 | + as it is still useful and can be stabilized such sooner, |
| 174 | + and it does not prevent adding another language feature later. |
| 175 | + |
| 176 | +* In response to “what if `Zeroable` is implemented for other types” |
| 177 | + it was suggested to prevent such `impl`s by making the trait permanently-unstable, |
| 178 | + or effectively private (by moving it in a private module |
| 179 | + and keeping it `pub trait` to fool the *private in public* lint). |
| 180 | + The author feels that such abuses of the stability or privacy systems |
| 181 | + do not belong in stable APIs. |
| 182 | + (Stable APIs that mention traits like `RangeArgument` that are not stable *yet* |
| 183 | + but have a path to stabilization are less of an abuse.) |
| 184 | + |
| 185 | +* Still, we could decide on some answer to “`Zeroable` for abitrary types”, |
| 186 | + implement and test it, stabilize `NonZero<T>` and `Zeroable` as-is |
| 187 | + (re-exported in `std`), and not add `num::NonZero*`. |
| 188 | + |
| 189 | +* Instead of `std::num` the new types could be in some other location, |
| 190 | + such as the modules named after their respective primitive types. |
| 191 | + For example `std::u32::NonZeroU32` or `std::u32::NonZero`. |
| 192 | + The former looks redundant, |
| 193 | + and the latter might lead to code that looks ambiguous if the type itself is imported |
| 194 | + instead of importing the module and using a qualified `u32::NonZero` path. |
| 195 | + |
| 196 | +* We could drop the `NonZeroI*` wrappers for signed integers. |
| 197 | + They’re included in this RFC because it’s easy, |
| 198 | + but every use of non-zero integers the author has seen so far has been with unsigned ones. |
| 199 | + This would cut the number of new types from 12 to 6. |
| 200 | + |
| 201 | +# Unresolved questions |
| 202 | +[unresolved]: #unresolved-questions |
| 203 | + |
| 204 | +Should the memory layout of e.g. `Option<NonZeroU32>` be a language guarantee? |
| 205 | + |
| 206 | +Discussion of the design of a new language feature |
| 207 | +for integer types restricted to an arbitrary sub-range (see second unresolved question) |
| 208 | +is out of scope for this RFC. |
| 209 | +Discussing the potential existence of such a feature |
| 210 | +as a reason **not** to add non-zero integer types is in scope. |
0 commit comments