rust-lang / rust-lang/libs-team

`OnlyNegative` and `NonNegative` to compliment `NonZero` for signed integers

Open
#814 4 comments 3 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

api-change-proposal
Dominant language
Rust
Stars
178
Forks
28
Avg merge
15m
Merged PRs (30d)
1

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.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading the linked internals.rust-lang.org discussion and the Rust feature lifecycle guidance referenced in the issue. Done means the libs-api team has resolved whether the proposed OnlyNegative and NonNegative types and their API belong in the standard library; no implementation files or tests are identified yet.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
api
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.