Skip to content

New API: Range::cmp_scalar; comparison (less/equal/greater) to a primitive of the Range #102343

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions library/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@
#![feature(const_slice_index)]
#![feature(const_is_char_boundary)]
#![feature(const_cstr_methods)]
#![feature(cmp_scalar)]
//
// Language features:
#![feature(abi_unadjusted)]
Expand Down
136 changes: 136 additions & 0 deletions library/core/src/ops/range.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,44 @@ impl<Idx: PartialOrd<Idx>> Range<Idx> {
}
}

impl<Idx: core::cmp::Ord> Range<Idx> {
/// Compares the range to a scalar. Returns `Ordering::Equal`,
/// if the range contains the scalar. If not, returns `Ordering::Less`
/// or `Ordering::Greater`, depending on whether the range is
/// below or above the scalar.
///
/// # Examples
///
/// ```rust
/// #![feature(cmp_scalar)]
/// # fn main() -> Result<(), usize> {
/// # struct File {
/// # seqnum: u32,
/// # range: core::ops::Range<usize>,
/// # }
/// let files = vec![
/// File { seqnum: 0, range: 0..1000 },
/// File { seqnum: 1, range: 1000..2200 },
/// File { seqnum: 2, range: 2200..3900 },
/// File { seqnum: 3, range: 3900..5000 },
/// ];
/// let target = 1600;
/// let index = files.binary_search_by(|f| f.range.cmp_scalar(target))?;
/// assert_eq!(files[index].seqnum, 1);
/// # Ok(()) }
/// ```
#[unstable(feature = "cmp_scalar", issue = "none")]
pub fn cmp_scalar(&self, scalar: Idx) -> core::cmp::Ordering {
if self.end <= scalar {
core::cmp::Ordering::Less
} else if scalar < self.start {
core::cmp::Ordering::Greater
} else {
core::cmp::Ordering::Equal
}
}
}

/// A range only bounded inclusively below (`start..`).
///
/// The `RangeFrom` `start..` contains all values with `x >= start`.
Expand Down Expand Up @@ -223,6 +261,26 @@ impl<Idx: PartialOrd<Idx>> RangeFrom<Idx> {
}
}

impl<Idx: core::cmp::Ord> RangeFrom<Idx> {
/// Compares the range to a scalar. Returns `Ordering::Equal`,
/// if the range contains the scalar. If not, the range
/// can be only above the scalar, so returns `Ordering::Greater`.
///
/// # Examples
///
/// ```rust
/// #![feature(cmp_scalar)]
/// # use core::cmp::Ordering;
/// assert_eq!((100..).cmp_scalar(50), Ordering::Greater);
/// assert_eq!((100..).cmp_scalar(100), Ordering::Equal);
/// assert_eq!((100..).cmp_scalar(150), Ordering::Equal);
/// ```
#[unstable(feature = "cmp_scalar", issue = "none")]
pub fn cmp_scalar(&self, scalar: Idx) -> core::cmp::Ordering {
if scalar < self.start { core::cmp::Ordering::Greater } else { core::cmp::Ordering::Equal }
}
}

/// A range only bounded exclusively above (`..end`).
///
/// The `RangeTo` `..end` contains all values with `x < end`.
Expand Down Expand Up @@ -304,6 +362,26 @@ impl<Idx: PartialOrd<Idx>> RangeTo<Idx> {
}
}

impl<Idx: core::cmp::Ord> RangeTo<Idx> {
/// Compares the range to a scalar. Returns `Ordering::Equal`,
/// if the range contains the scalar. If not, the range
/// can be only below the scalar, so returns `Ordering::Less`.
///
/// # Examples
///
/// ```rust
/// #![feature(cmp_scalar)]
/// # use core::cmp::Ordering;
/// assert_eq!((..100).cmp_scalar(50), Ordering::Equal);
/// assert_eq!((..100).cmp_scalar(100), Ordering::Less);
/// assert_eq!((..100).cmp_scalar(150), Ordering::Less);
/// ```
#[unstable(feature = "cmp_scalar", issue = "none")]
pub fn cmp_scalar(&self, scalar: Idx) -> core::cmp::Ordering {
if self.end <= scalar { core::cmp::Ordering::Less } else { core::cmp::Ordering::Equal }
}
}

/// A range bounded inclusively below and above (`start..=end`).
///
/// The `RangeInclusive` `start..=end` contains all values with `x >= start`
Expand Down Expand Up @@ -541,6 +619,44 @@ impl<Idx: PartialOrd<Idx>> RangeInclusive<Idx> {
}
}

impl<Idx: core::cmp::Ord> RangeInclusive<Idx> {
/// Compares the range to a scalar. Returns `Ordering::Equal`,
/// if the range contains the scalar. If not, returns `Ordering::Less`
/// or `Ordering::Greater`, depending on whether the range is
/// below or above the scalar.
///
/// # Examples
///
/// ```rust
/// #![feature(cmp_scalar)]
/// # fn main() -> Result<(), usize> {
/// # struct File {
/// # seqnum: u32,
/// # range: core::ops::RangeInclusive<usize>,
/// # }
/// let files = vec![
/// File { seqnum: 0, range: 0..=999 },
/// File { seqnum: 1, range: 1000..=2199 },
/// File { seqnum: 2, range: 2200..=3899 },
/// File { seqnum: 3, range: 3900..=4999 },
/// ];
/// let target = 1600;
/// let index = files.binary_search_by(|f| f.range.cmp_scalar(target))?;
/// assert_eq!(files[index].seqnum, 1);
/// # Ok(()) }
/// ```
#[unstable(feature = "cmp_scalar", issue = "none")]
pub fn cmp_scalar(&self, scalar: Idx) -> core::cmp::Ordering {
if *self.end() < scalar {
core::cmp::Ordering::Less
} else if scalar < *self.start() {
core::cmp::Ordering::Greater
} else {
core::cmp::Ordering::Equal
}
}
}

/// A range only bounded inclusively above (`..=end`).
///
/// The `RangeToInclusive` `..=end` contains all values with `x <= end`.
Expand Down Expand Up @@ -622,6 +738,26 @@ impl<Idx: PartialOrd<Idx>> RangeToInclusive<Idx> {
}
}

impl<Idx: core::cmp::Ord> RangeToInclusive<Idx> {
/// Compares the range to a scalar. Returns `Ordering::Equal`,
/// if the range contains the scalar. If not, the range
/// can be only below the scalar, so returns `Ordering::Less`.
///
/// # Examples
///
/// ```rust
/// #![feature(cmp_scalar)]
/// # use core::cmp::Ordering;
/// assert_eq!((..=100).cmp_scalar(50), Ordering::Equal);
/// assert_eq!((..=100).cmp_scalar(100), Ordering::Equal);
/// assert_eq!((..=100).cmp_scalar(150), Ordering::Less);
/// ```
#[unstable(feature = "cmp_scalar", issue = "none")]
pub fn cmp_scalar(&self, scalar: Idx) -> core::cmp::Ordering {
if self.end < scalar { core::cmp::Ordering::Less } else { core::cmp::Ordering::Equal }
}
}

// RangeToInclusive<Idx> cannot impl From<RangeTo<Idx>>
// because underflow would be possible with (..0).into()

Expand Down
1 change: 1 addition & 0 deletions library/core/tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@
#![feature(slice_flatten)]
#![feature(provide_any)]
#![feature(utf8_chunks)]
#![feature(cmp_scalar)]
#![deny(unsafe_op_in_unsafe_fn)]

extern crate test;
Expand Down
200 changes: 1 addition & 199 deletions library/core/tests/ops.rs
Original file line number Diff line number Diff line change
@@ -1,206 +1,8 @@
mod control_flow;
mod range;

use core::ops::{Bound, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive};
use core::ops::{Deref, DerefMut};

// Test the Range structs and syntax.

#[test]
fn test_range() {
let r = Range { start: 2, end: 10 };
let mut count = 0;
for (i, ri) in r.enumerate() {
assert_eq!(ri, i + 2);
assert!(ri >= 2 && ri < 10);
count += 1;
}
assert_eq!(count, 8);
}

#[test]
fn test_range_from() {
let r = RangeFrom { start: 2 };
let mut count = 0;
for (i, ri) in r.take(10).enumerate() {
assert_eq!(ri, i + 2);
assert!(ri >= 2 && ri < 12);
count += 1;
}
assert_eq!(count, 10);
}

#[test]
fn test_range_to() {
// Not much to test.
let _ = RangeTo { end: 42 };
}

#[test]
fn test_full_range() {
// Not much to test.
let _ = RangeFull;
}

#[test]
fn test_range_inclusive() {
let mut r = RangeInclusive::new(1i8, 2);
assert_eq!(r.next(), Some(1));
assert_eq!(r.next(), Some(2));
assert_eq!(r.next(), None);

r = RangeInclusive::new(127i8, 127);
assert_eq!(r.next(), Some(127));
assert_eq!(r.next(), None);

r = RangeInclusive::new(-128i8, -128);
assert_eq!(r.next_back(), Some(-128));
assert_eq!(r.next_back(), None);

// degenerate
r = RangeInclusive::new(1, -1);
assert_eq!(r.size_hint(), (0, Some(0)));
assert_eq!(r.next(), None);
}

#[test]
fn test_range_to_inclusive() {
// Not much to test.
let _ = RangeToInclusive { end: 42 };
}

#[test]
fn test_range_is_empty() {
assert!(!(0.0..10.0).is_empty());
assert!((-0.0..0.0).is_empty());
assert!((10.0..0.0).is_empty());

assert!(!(f32::NEG_INFINITY..f32::INFINITY).is_empty());
assert!((f32::EPSILON..f32::NAN).is_empty());
assert!((f32::NAN..f32::EPSILON).is_empty());
assert!((f32::NAN..f32::NAN).is_empty());

assert!(!(0.0..=10.0).is_empty());
assert!(!(-0.0..=0.0).is_empty());
assert!((10.0..=0.0).is_empty());

assert!(!(f32::NEG_INFINITY..=f32::INFINITY).is_empty());
assert!((f32::EPSILON..=f32::NAN).is_empty());
assert!((f32::NAN..=f32::EPSILON).is_empty());
assert!((f32::NAN..=f32::NAN).is_empty());
}

#[test]
fn test_bound_cloned_unbounded() {
assert_eq!(Bound::<&u32>::Unbounded.cloned(), Bound::Unbounded);
}

#[test]
fn test_bound_cloned_included() {
assert_eq!(Bound::Included(&3).cloned(), Bound::Included(3));
}

#[test]
fn test_bound_cloned_excluded() {
assert_eq!(Bound::Excluded(&3).cloned(), Bound::Excluded(3));
}

#[test]
#[allow(unused_comparisons)]
#[allow(unused_mut)]
fn test_range_syntax() {
let mut count = 0;
for i in 0_usize..10 {
assert!(i >= 0 && i < 10);
count += i;
}
assert_eq!(count, 45);

let mut count = 0;
let mut range = 0_usize..10;
for i in range {
assert!(i >= 0 && i < 10);
count += i;
}
assert_eq!(count, 45);

let mut count = 0;
let mut rf = 3_usize..;
for i in rf.take(10) {
assert!(i >= 3 && i < 13);
count += i;
}
assert_eq!(count, 75);

let _ = 0_usize..4 + 4 - 3;

fn foo() -> isize {
42
}
let _ = 0..foo();

let _ = { &42..&100 }; // references to literals are OK
let _ = ..42_usize;

// Test we can use two different types with a common supertype.
let x = &42;
{
let y = 42;
let _ = x..&y;
}
}

#[test]
#[allow(dead_code)]
fn test_range_syntax_in_return_statement() {
fn return_range_to() -> RangeTo<i32> {
return ..1;
}
fn return_full_range() -> RangeFull {
return ..;
}
// Not much to test.
}

#[test]
fn range_structural_match() {
// test that all range types can be structurally matched upon

const RANGE: Range<usize> = 0..1000;
match RANGE {
RANGE => {}
_ => unreachable!(),
}

const RANGE_FROM: RangeFrom<usize> = 0..;
match RANGE_FROM {
RANGE_FROM => {}
_ => unreachable!(),
}

const RANGE_FULL: RangeFull = ..;
match RANGE_FULL {
RANGE_FULL => {}
}

const RANGE_INCLUSIVE: RangeInclusive<usize> = 0..=999;
match RANGE_INCLUSIVE {
RANGE_INCLUSIVE => {}
_ => unreachable!(),
}

const RANGE_TO: RangeTo<usize> = ..1000;
match RANGE_TO {
RANGE_TO => {}
_ => unreachable!(),
}

const RANGE_TO_INCLUSIVE: RangeToInclusive<usize> = ..=999;
match RANGE_TO_INCLUSIVE {
RANGE_TO_INCLUSIVE => {}
_ => unreachable!(),
}
}

// Test Deref implementations

#[test]
Expand Down
Loading