Skip to content

Commit b728bf6

Browse files
authored
Merge pull request #2307 from SimonSapin/concrete-nonzero
RFC: Add std::num::NonZeroU32 and friends, deprecate core::nonzero
2 parents dfad2c9 + 0d66c67 commit b728bf6

File tree

1 file changed

+210
-0
lines changed

1 file changed

+210
-0
lines changed

text/2307-concrete-nonzero-types.md

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
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

Comments
 (0)