Skip to content

OnlyNegative and NonNegative to compliment NonZero for signed integers #814

Open

Description

@ds84182

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

  1. cuviper commented on Jun 15, 2026

    @cuviper
    Member

    Result, which, despite the absence of layout guarantees, would still have the repr as i32.

    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 Result discriminant, 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

  2. scottmcm commented on Jun 16, 2026

    @scottmcm
    Member

    but the compiler doesn't know how to figure that out yet.

    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 -- positive32 could be both Into<u32> and Into<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.)

  3. kennytm commented on Jun 16, 2026

    @kennytm
    Member

    I do really like the "intersection of uN and iN" types particularly -- positive32 could be both Into<u32> and Into<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.)

    positive32 (NonNegative<i32>) is just u31 which can also arrive through rust-lang/rfcs#3686 instead of pattern type.

  4. joshtriplett commented on Aug 1, 2026

    @joshtriplett
    Member

    I'd love to see us ship a way to define these kinds of disjoint Result types 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions