rust-lang / rust-lang/libs-team

Add std::range::DecreasingRange* structs

Open
#783 17 comments 1 reaction 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

Sometimes you want to have a range going down from the top rather than counting up from the bottom. While it is possible to use Iterator::rev() to reverse an incrementing range, the inclusion/exclusion of the start and end get flipped, leading to confusing code.

Motivating examples or use cases

From some code of mine that checked tiebreak comparisons

    for jjj in 0..=4 {
        // Go from 4 to 0 rather than the other way around
        let iii = 4 - jjj;
        let p1_breaker = hand1.tiebreaks[iii];
        let p2_breaker = hand2.tiebreaks[iii];
        if p1_breaker > p2_breaker {
            return true;
        } else if p2_breaker > p1_breaker {
            return false;
        }
    }

I thought that having explicit subtraction was more readable than

    for iii in (0..=4).rev() {
        // ...
    }

but having a dedicated range type for decreasing ranges would have been even clearer:

    for iii in (DecreasingRangeInclusive{start: 4, end: 0}) {
        // ...
    }

Solution sketch

Sketch based on new std::range structs - all current std::range::* structs have a corresponding struct proposed here for completeness

// Corresponds to std::range::Range
/// A (half-open) range bounded inclusively above and exclusively below.
/// The DecreasingRange contains all values with end < x <= start. It is empty if start <= end.
#[derive(Copy, Hash)]
#[derive_const(Clone, Default, PartialEq, Eq)]
pub struct DecreasingRange<Idx> {
    pub start: Idx;
    pub end: Idx;
}

impl<Idx> DecreasingRange<Idx>
where
    Idx: std::iter::Step
{
    pub fn iter(&self) -> DecreasingRangeIter<Idx> {}
}

impl<Idx> DecreasingRange<Idx>
where
    Idx: PartialOrd
{
    pub fn contains<U>(&self, item: &U) -> bool
    where
        Idx: PartialOrd<U>
        U: PartialOrd<Idx> + ?Sized
    {}

    pub fn is_empty(&self) -> bool
    where
        Idx: PartialOrd
    {}
}

// DecreasingRange trait implementations, not showing everything here
impl<Idx> Debug for DecreasingRange<Idx> where Idx: Debug {}
impl<T> RangeBounds<T> for DecreasingRange<&T> {}
impl<T> RangeBounds<T> for DecreasingRange<T> {}

// Corresponds to std::range::RangeFrom
/// A range only bounded inclusively above.
/// The RangeFrom contains all values with start <= x.
#[derive(Copy, Hash)]
#[derive_const(Clone, PartialEq, Eq)]
pub struct DecreasingRangeFrom<Idx> {
    pub start: Idx,
}

impl<Idx> DecreasingRangeFrom<Idx>
where
    Idx: std::iter::Step
{
    pub fn iter(&self) -> DecreasingRangeFromIter<Idx> {}
}

impl<Idx> DecreasingRangeFrom<Idx>
where
    Idx: PartialOrd
{
    pub fn contains<U>(&self, item: &U) -> bool
    where
        Idx: PartialOrd<U>
        U: PartialOrd<Idx> + ?Sized
    {}
}

// DecreasingRangeFrom trait implementations, not showing everything here
impl<Idx> Debug for DecreasingRangeFrom<Idx> where Idx: Debug {}
impl<T> RangeBounds<T> for DecreasingRangeFrom<&T> {}
impl<T> RangeBounds<T> for DecreasingRangeFrom<T> {}

// Corresponds to std::range::RangeFromIter
/// By-value DecreasingRangeFrom iterator.
pub struct DecreasingRangeFromIter<A> {}
impl<A> Iterator for DecreasingRangeFromIter<A> where A: Step {}

// Corresponds to std::range::DecreasingRangeInclusive
/// A range bounded inclusively below and above.
/// The DecreasingRangeInclusive contains all values with end <= x <= start. It is empty unless start >= end.
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct DecreasingRangeInclusive<Idx> {
    pub start: Idx,
    pub end: Idx,
}

impl<Idx> DecreasingRangeInclusive<Idx>
where
    Idx: std::iter::Step
{
    pub fn iter(&self) -> DecreasingRangeInclusiveIter<Idx> {}
}

impl<Idx> DecreasingRangeInclusive<Idx>
where
    Idx: PartialOrd
{
    pub fn contains<U>(&self, item: &U) -> bool
    where
        Idx: PartialOrd<U>
        U: PartialOrd<Idx> + ?Sized
    {}

    pub fn is_empty(&self) -> bool
    where
        Idx: PartialOrd
    {}
}

// DecreasingRangeInclusive trait implementations, not showing everything here
impl<Idx> Debug for DecreasingRangeInclusive<Idx> where Idx: Debug {}
impl<T> RangeBounds<T> for DecreasingRangeInclusive<&T> {}
impl<T> RangeBounds<T> for DecreasingRangeInclusive<T> {}

// Corresponds to std::range::RangeInclusiveIter
/// By-value DecreasingRangeInclusive iterator.
pub struct DecreasingRangeInclusiveIter<A> {}
impl<A> Iterator for DecreasingRangeInclusiveIter<A> where A: Step {}

// Corresponds to std::range::RangeIter
/// By-value DecreasingRange iterator.
pub struct DecreasingRangeIter<A> {}
impl<A> Iterator for DecreasingRangeIter<A> where A: Step {}

// Corresponds to std::range::RangeToInclusive
/// A range bounded only inclusively below.
/// The DecreasingRangeToInclusive contains all values last <= x. It cannot serve
/// as an Iterator because it doesn't have a starting point.
pub struct DecreasingRangeToInclusive<Idx> {
    pub last: Idx,
}

impl<Idx> DecreasingRangeToInclusive<Idx>
where
    Idx: PartialOrd
{
    pub fn contains<U>(&self, item: &U) -> bool
    where
        Idx: PartialOrd<U>
        U: PartialOrd<Idx> + ?Sized
    {}
}

Alternatives

  • Do nothing and prefer using .rev()

Links and related work

rust-lang/rfcs#3550

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

No repository files or tests are identified. Start by reading the proposed std::range types, rust-lang/rfcs#3550, and the linked feature lifecycle; done means the libs-api team has resolved the proposal and, if approved, an implementation scope is agreed for a follow-up PR.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
api, backend-api-design
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.