Skip to content

Commit 0bc239c

Browse files
committed
publish niche post!
1 parent 16ba881 commit 0bc239c

1 file changed

Lines changed: 22 additions & 79 deletions

File tree

content/posts/2026-05-15-niche-int-types-in-rust.md renamed to content/posts/2026-05-04-niche-int-types-in-rust.md

Lines changed: 22 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
---
22
title: 'Niches for integer types in Rust'
3-
publishDate: '2026-05-15'
4-
updatedAt: '2026-05-15'
5-
draft: true
3+
publishDate: '2026-05-04'
4+
updatedAt: '2026-05-04'
65
categories:
76
- rust
87
- type-system
@@ -140,7 +139,7 @@ impl Pos<Zero> {
140139
This works, and `Option<Pos<Zero>>` is now 4 bytes.
141140
But every `new` adds 1 and every `get` subtracts 1.
142141
It's a single ALU instruction each time,
143-
so the cost is negligible in practice.
142+
so the cost is probably negligible in most code paths.
144143
Still, it's conceptually unsatisfying:
145144
we're contorting the representation
146145
to fit a niche that doesn't match our actual invariant.
@@ -153,92 +152,40 @@ to fit a niche that doesn't match our actual invariant.
153152
As of Rust 1.95 (May 2026),
154153
there is no stable way to tell the compiler
155154
"this `u32` only holds values `0..=0x7FFF_FFFF`."
156-
But internally, the standard library does exactly that for its own types
157-
using the attributes
158-
`rustc_layout_scalar_valid_range_start` and `rustc_layout_scalar_valid_range_end`.
159-
On nightly, we can use them too:
160-
161-
```rust {.wide}
162-
#![feature(rustc_attrs)]
163-
164-
#[rustc_layout_scalar_valid_range_start(0)]
165-
#[rustc_layout_scalar_valid_range_end(0x7FFF_FFFF)] // i32::MAX
166-
#[repr(transparent)]
167-
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
168-
pub struct Pos(u32);
169-
```
170-
171-
This tells the compiler the full valid range,
172-
and every bit pattern outside it becomes a niche.
173-
No bias, no XOR, no runtime cost at all.
174-
The `get` accessor is a plain field read:
175-
176-
```rust {.wide}
177-
impl TryFrom<i32> for Pos {
178-
type Error = ();
179-
180-
fn try_from(value: i32) -> Result<Self, Self::Error> {
181-
if value < 0 { return Err(()); }
182-
// SAFETY: value is in 0..=i32::MAX
183-
Ok(unsafe { Self(value as u32) })
184-
}
185-
}
186155

187-
impl Pos {
188-
pub const fn get(self) -> i32 {
189-
// zero-cost: the u32 is already in range
190-
self.0 as i32
191-
}
192-
}
193-
```
194-
195-
And we get the niche for free:
196-
197-
```rust {.wide}
198-
assert_eq!(size_of::<Pos>(), 4);
199-
assert_eq!(size_of::<Option<Pos>>(), 4);
200-
```
201-
202-
You can see a [little test script][play1] here.
203-
204-
[play1]: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2024&gist=f5b9731872fc5762c2c3fba0c1af6007 "Rust playground"
205-
206-
There's a catch, though:
207-
these attributes make every construction of the type `unsafe`.
208-
Any code that writes to the inner `u32`
209-
must uphold the range invariant,
210-
and the compiler won't check it for you.
211-
That's fine when it's behind a `new` constructor,
212-
but it does mean we need to restrict how this can be used.
156+
~~But internally, the standard library does exactly that for its own types
157+
using the attributes
158+
`rustc_layout_scalar_valid_range_start` and `rustc_layout_scalar_valid_range_end`.~~
213159

214-
These attributes are not a public API.
215-
They are clearly marked as `rustc_attrs`
216-
which sounds *very* internal.
160+
**Oh wait -- while I was writing this post,
161+
this exact feature got replaced!**
217162

218163
## On nightly: Pattern types
219164

220165
While researching this,
221166
I came across [this issue][rust-135996]
222-
where Oli proposes using a "pattern types" feature.
223-
So it seems there is *another* way of doing this!
224-
While reading through the [tracking issue][rust-123646] and Zulip thread,
167+
where [Oli] proposes using a "pattern types" feature.
168+
So it seems there is new way of doing this!
169+
While reading through the [tracking issue][rust-123646] and [Zulip channel],
225170
I found this [pre-RFC document][pre-rfc]
226171
(last updated in 2024 but discussed further in 2025).
227172
What is in the standard library right now
228173
on nightly is a [`pattern_type!`] macro.
229174
[Rust PR 136006] has some usage of this so I could put this together:
230175

176+
[Oli]: https://github.com/oli-obk
231177
[rust-135996]: https://github.com/rust-lang/rust/issues/135996 "Replace rustc_layout_scalar_valid_range_start attribute with pattern types"
232-
[rust-123646]: https://github.com/rust-lang/rust/issues/123646 "Tracking Issue for pattern types"
178+
[rust-123646]: https://github.com/rust-lang/rust/issues/123646
233179
[pre-rfc]: https://gist.github.com/joboet/0cecbce925ee2ad1ee3e5520cec81e30
234180
[`pattern_type!`]: https://doc.rust-lang.org/1.95.0/core/macro.pattern_type.html "core::pattern_type!"
235181
[Rust PR 136006]: https://github.com/rust-lang/rust/pull/136006
182+
[Zulip channel]: https://rust-lang.zulipchat.com/#narrow/channel/481660-t-lang.2Fpattern-types
236183

237184
```rust {.wide}
238185
#![feature(pattern_types)]
239186
#![feature(pattern_type_macro)]
240187

241-
pub struct Pos(pattern_type!(i32 is 0..));
188+
pub struct Pos(pattern_type!(i32 is 0..=i32::MAX));
242189

243190
impl Pos {
244191
pub const fn new(value: i32) -> Option<Self> {
@@ -261,22 +208,18 @@ impl Pos {
261208

262209
[play2]: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2024&gist=d4c7bf6936d2bcef89455e7e271fba76
263210

264-
I'm not sure about the `transmute` being the *best* way to do this,
265-
but `pattern_type!` produces a new type
266-
and I couldn't figure out how to construct it otherwise.
267-
268-
Given the lack of actual documentation and RFC[^missed],
269-
I think it's fair to classify this feature
270-
as even more nightly than the other one.
271-
272-
[^missed]: Or did I miss it?
211+
For now, `transmute` from the underlying type is the only way
212+
to construct the pattern type.
213+
On Zulip, Oli also recommended using inclusive ranges.
273214

274215
## Conclusion
275216

276217
I'm not using any of these nightly features in the real code yet,
277218
but I'm glad to see momentum in this space.
278219
It's a feature I've wanted in a few places already.
279-
Another use case is a proper type for an "inline length" type,
280-
removing a workaround like the one `SmolStr` uses [here][smol_str len].
220+
Other use cases for patterns type are an "inline length" type,
221+
removing a workaround like the one `SmolStr` uses [here][smol_str len],
222+
or types that have sentinels by specification,
223+
like `INT8` in BAM files which actuallys is `-120..=127`.
281224

282225
[smol_str len]: https://github.com/rust-lang/rust-analyzer/blob/4a244d4c6bf18bae57626dcaf81bf6442ad59380/lib/smol_str/src/lib.rs#L541-L569

0 commit comments

Comments
 (0)