Repository navigation
Description
Proposal
Problem statement
Currently there is no way to express an integer is always negative or non negative. Enums cannot take advantage of niches for these cases.
Motivating examples or use cases
One example is HRESULT (on Windows) and Result (on Nintendo's Horizon OS). The error values for these are negative (as in they have the MSB set), and zero/positive values are used to indicate success.
I would like to implement the Result type for Horizon as:
pub struct Error(OnlyNegative<i32>);
impl Error { /* ... helpers to create valid error types from level, summary, desc... accessors for these. */ }
pub struct Success(NonNegative<i32>);
pub type Result = core::result::Result<Success, Error>;
#[repr(transparent)]
pub struct RawResult(pub i32);
impl From<RawResult> for Result {
fn from(res: RawResult) {
match NonNegative::new(res.0) {
Ok(v) => Ok(Success(v)),
Err(v) => Err(Error(v)),
}
}
}Where RawResult is FFI-compatible and can be turned into Result, which, despite the absence of layout guarantees, would still have the repr as i32. Result::from should therefore compile to a no-op.
Solution sketch
// Perma-unstable, implemented for i8, i16, i32, i64, i128, and isize:
pub trait SignedInt: Copy + PartialEq + Eq + PartialOrd + Ord + Debug + ... + Sealed {
type Unsigned;
const MIN: Self;
const ZERO: Self;
}
// NOTE: Does _not_ include zero, as the expectation is that the sign bit is always set, and there
// is no signed zero for two's compliment integers.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct OnlyNegative<T: SignedInt>(core::pattern_type!(T is T::MIN..T::ZERO));
impl<T: SignedInt> Debug for OnlyNegative<T> { /* passthrough */ }
impl<T: SignedInt> Display for OnlyNegative<T> { /* passthrough */ }
impl<T: SignedInt> OnlyNegative<T> {
pub const fn new(value: T) -> Result<Self, NonNegative<T>> { /* ... */ }
pub const fn get(self) -> T { /* ... */ }
// Returns an unsigned value because -T::MIN overflows T.
pub const fn abs(self) -> T::Unsigned { /* ... */ }
// ... checked arithmetic, like NonZero
}
// Question: Should we also allow T to be unsigned? Semantically it makes sense, but it would effectively
// be a no-op.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct NonNegative<T: SignedInt>(core::pattern_type!(T is T::ZERO..));
impl<T: SignedInt> Debug for NonNegative<T> { /* passthrough */ }
impl<T: SignedInt> Display for NonNegative<T> { /* passthrough */ }
impl<T: SignedInt> NonNegative<T> {
pub const fn new(value: T) -> Result<Self, OnlyNegative<T>> { /* ... */ }
pub const fn get(self) -> T::Unsigned { /* ... */ }
// ... checked arithmetic, like NonZero
}Alternatives
An alternative to this is pattern types, but they are currently very unstable (in fact pattern_types feature is marked as compiler internal). There is also no proposed RFC for them as far as I'm aware.
Links and related work
What happens now?
This issue contains an API change proposal (or ACP) and is part of the libs-api team feature lifecycle. Once this issue is filed, the libs-api team will review open proposals as capability becomes available. Current response times do not have a clear estimate, but may be up to several months.
Possible responses
The libs team may respond in various different ways. First, the team will consider the problem (this doesn't require any concrete solution or alternatives to have been proposed):
- We think this problem seems worth solving, and the standard library might be the right place to solve it.
- We think that this probably doesn't belong in the standard library.
Second, if there's a concrete solution:
- We think this specific solution looks roughly right, approved, you or someone else should implement this. (Further review will still happen on the subsequent implementation PR.)
- We're not sure this is the right solution, and the alternatives or other materials don't give us enough information to be sure about that. Here are some questions we have that aren't answered, or rough ideas about alternatives we'd want to see discussed.
Activity
cuviper commentedon Jun 15, 2026
In theory this could be true, but the compiler doesn't know how to figure that out yet. It can find a niche in either side to use as the
Resultdiscriminant, but that still requires separate space for the other data type as well. It doesn't use entirely non-overlapping types the way you want.e.g. playground
scottmcm commentedon Jun 16, 2026
This was discussed a bit recently on the forums as well, https://users.rust-lang.org/t/stringlet-utf-8-hack-option-niche/140097/10?u=scottmcm
It "just" needs someone to do the work, though (as opposed to figuring out opsem questions or stabilizing new stuff or getting RFCs approved).
I do really like the "intersection of uN and iN" types particularly --
positive32could be bothInto<u32>andInto<i32>-- but it's not obvious that we should add these things as distinct library types since there's just so many possible wishes and perhaps any effort should go into the more-general form of pattern types instead, even if they're further off. (Especially if the motivating layout optimization example doesn't work yet.)kennytm commentedon Jun 16, 2026
positive32(NonNegative<i32>) is justu31which can also arrive through rust-lang/rfcs#3686 instead of pattern type.joshtriplett commentedon Aug 1, 2026
I'd love to see us ship a way to define these kinds of disjoint
Resulttypes cleanly. Once the compiler has a mechanism for guaranteeing the layout, we should ship the corresponding types.Note that the Linux kernel has a similar but different mechanism: values from -1 to -4095 are errors, other values are successes. Hopefully whatever mechanism we define is sufficiently general to support both.