Skip to content
This repository was archived by the owner on Sep 24, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
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
14 changes: 12 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,20 @@ jobs:
- name: Format
run: cargo fmt --check
- name: Lint
run: cargo clippy --all
run: cargo clippy --all-targets --all
- name: Build
run: cargo build --all --verbose
- name: Run tests
run: cargo test --all --verbose
- name: Generate the documentation
run: cargo doc --all --verbose
run: cargo doc --all --no-deps --verbose
- name: Verify the MSRV (1.35)
run: |
rustup toolchain install 1.35.0 --profile minimal --no-self-update
cargo +1.35.0 test --all --verbose
- name: Build without std (bare-metal target)
run: |
rustup target add thumbv7em-none-eabi
cargo build --target thumbv7em-none-eabi --verbose
- name: Verify the crate packages cleanly
run: cargo publish --dry-run
58 changes: 58 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Changelog

All notable changes to this project are documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added

- The crate is now `#![no_std]`. It has no dependencies and relies only on `core`,
so it can be used in embedded and other environments without the standard library.
- Cargo manifest metadata for crates.io: `keywords`, `categories`, `rust-version`,
and `readme`.

### Changed

- Lowered the MSRV to **1.35** (the floor of the `RangeBounds` API) by switching to
edition 2018 and avoiding later-stabilized conveniences (nested or-patterns,
the `matches!` macro, and the `f64::NAN` associated constant). No API changes.

## [0.3.0]

### Changed

- **Breaking:** reworked the comparison semantics to handle empty ranges explicitly
and to be honest about partial orders.
- **Breaking:** renamed the `range_cmp` method to `rcmp` and the `RangeComparable`
trait to `RangeOrd`.

### Added

- `RangeOrdering::Empty`: empty and inverted ranges (such as `45..35` or `0..0`) are
now reported explicitly instead of returning an arbitrary, representation-dependent
verdict. Emptiness is judged from the bounds (so `..0u32` is non-empty). Resolves
[#6](https://github.com/Akvize/range-cmp/issues/6).
- `PartialRangeOrd` trait with a `partial_rcmp` method for types that are `PartialOrd`
but not `Ord`.
- `RangePosition` (the pair of a value's relationships to the lower and upper bounds,
with `is_inside` and `ordering` helpers) and `BoundOrdering`
(`Within` / `Outside` / `Incomparable`), which preserve per-bound information over
partial orders.

## [0.2.0]

### Changed

- **Breaking:** comparison now first checks whether the range contains the value.

## [0.1.3]

Initial published releases (`0.1.0` through `0.1.3`).

[Unreleased]: https://github.com/Akvize/range-cmp/compare/v0.3.0...HEAD
[0.3.0]: https://github.com/Akvize/range-cmp/compare/v0.2.0...v0.3.0
[0.2.0]: https://github.com/Akvize/range-cmp/compare/v0.1.3...v0.2.0
[0.1.3]: https://github.com/Akvize/range-cmp/releases/tag/v0.1.3
6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
[package]
name = "range-cmp"
version = "0.3.0"
edition = "2021"
edition = "2018"
rust-version = "1.35"
license = "MIT OR Apache-2.0"
description = "Trait that allows comparing a value to a range of values"
repository = "https://github.com/Akvize/range-cmp"
readme = "README.md"
keywords = ["range", "comparison", "ordering", "ord", "cmp"]
categories = ["algorithms", "no-std", "rust-patterns"]
exclude = [
"pre-commit",
"CONTRIBUTING.md",
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# range_cmp
# range-cmp

[![Crates.io][crates-badge]][crates-url]
[![MIT licensed][mit-badge]][mit-url]
Expand Down Expand Up @@ -27,6 +27,10 @@ assert_eq!(25.rcmp(20..30), RangeOrdering::Inside);
assert_eq!(35.rcmp(20..30), RangeOrdering::Above);
```

The crate is `#![no_std]` and has zero dependencies, so it can be used in embedded
and other environments without the standard library. Its MSRV (minimum supported
Rust version) is 1.35.

## Empty ranges handling

Empty ranges are handled explicitly, instead of returning an arbitrary,
Expand Down
35 changes: 20 additions & 15 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,20 @@
//! assert_eq!(2.5_f64.partial_rcmp(2.0..3.0).ordering(), Some(RangeOrdering::Inside));
//! assert_eq!(3.5_f64.partial_rcmp(2.0..3.0).ordering(), Some(RangeOrdering::Above));
//! // `NaN` is incomparable with the bounds, so there is no single verdict:
//! assert_eq!(f64::NAN.partial_rcmp(2.0..3.0).ordering(), None);
//! assert_eq!(core::f64::NAN.partial_rcmp(2.0..3.0).ordering(), None);
//! ```
//!
//! # `no_std`
//!
//! The crate is `#![no_std]`: it has no dependencies and only relies on `core`, so it
//! can be used in embedded and other environments without the standard library.
#![cfg_attr(not(test), no_std)]
#![forbid(unsafe_code)]
#![warn(missing_docs)]

use std::borrow::Borrow;
use std::cmp::Ordering;
use std::ops::{Bound, RangeBounds};
use core::borrow::Borrow;
use core::cmp::Ordering;
use core::ops::{Bound, RangeBounds};

/// Simplified result for [`RangeOrd::rcmp`], obtained for totally ordered types or by
/// collapsing a [`RangePosition`] through [`RangePosition::ordering`].
Expand Down Expand Up @@ -113,10 +121,7 @@ impl RangePosition {
/// assert!(!3.5_f64.partial_rcmp(2.0..3.0).is_inside());
/// ```
pub fn is_inside(&self) -> bool {
matches!(
(self.lower, self.upper),
(BoundOrdering::Within, BoundOrdering::Within)
)
self.lower == BoundOrdering::Within && self.upper == BoundOrdering::Within
}

/// Collapses the pair into a simple [`RangeOrdering`] when possible.
Expand Down Expand Up @@ -146,11 +151,11 @@ fn lower_ordering<T: PartialOrd>(value: &T, bound: Bound<&T>) -> BoundOrdering {
Bound::Unbounded => BoundOrdering::Within,
Bound::Included(key) => match value.partial_cmp(key) {
Some(Ordering::Less) => BoundOrdering::Outside,
Some(Ordering::Equal | Ordering::Greater) => BoundOrdering::Within,
Some(Ordering::Equal) | Some(Ordering::Greater) => BoundOrdering::Within,
None => BoundOrdering::Incomparable,
},
Bound::Excluded(key) => match value.partial_cmp(key) {
Some(Ordering::Less | Ordering::Equal) => BoundOrdering::Outside,
Some(Ordering::Less) | Some(Ordering::Equal) => BoundOrdering::Outside,
Some(Ordering::Greater) => BoundOrdering::Within,
None => BoundOrdering::Incomparable,
},
Expand All @@ -163,11 +168,11 @@ fn upper_ordering<T: PartialOrd>(value: &T, bound: Bound<&T>) -> BoundOrdering {
Bound::Unbounded => BoundOrdering::Within,
Bound::Included(key) => match value.partial_cmp(key) {
Some(Ordering::Greater) => BoundOrdering::Outside,
Some(Ordering::Equal | Ordering::Less) => BoundOrdering::Within,
Some(Ordering::Equal) | Some(Ordering::Less) => BoundOrdering::Within,
None => BoundOrdering::Incomparable,
},
Bound::Excluded(key) => match value.partial_cmp(key) {
Some(Ordering::Greater | Ordering::Equal) => BoundOrdering::Outside,
Some(Ordering::Greater) | Some(Ordering::Equal) => BoundOrdering::Outside,
Some(Ordering::Less) => BoundOrdering::Within,
None => BoundOrdering::Incomparable,
},
Expand Down Expand Up @@ -241,9 +246,9 @@ fn range_is_empty<T: Ord, R: RangeBounds<T>>(range: &R) -> bool {
/// | ^ cannot infer type of the type parameter `R` declared on the function `f`
/// ```
///
/// Indeed, although we understand we want to pass a [`Range`](std::ops::Range)`<`[`i32`]`>` by
/// Indeed, although we understand we want to pass a [`Range`](core::ops::Range)`<`[`i32`]`>` by
/// reference, the compiler need to assume that other types could yield a
/// `&`[`Range`](std::ops::Range)`<`[`i32`]`>` when borrowed.
/// `&`[`Range`](core::ops::Range)`<`[`i32`]`>` when borrowed.
pub trait BorrowRange<T: ?Sized, R>: Borrow<R> {}
impl<T, R: RangeBounds<T>> BorrowRange<T, R> for R {}
impl<T, R: RangeBounds<T>> BorrowRange<T, R> for &R {}
Expand Down Expand Up @@ -295,7 +300,7 @@ pub trait PartialRangeOrd {
/// assert_eq!(2.5_f64.partial_rcmp(2.0..3.0).ordering(), Some(RangeOrdering::Inside));
/// assert_eq!(3.5_f64.partial_rcmp(2.0..3.0).ordering(), Some(RangeOrdering::Above));
/// // `NaN` is incomparable with the bounds:
/// assert_eq!(f64::NAN.partial_rcmp(2.0..3.0).ordering(), None);
/// assert_eq!(core::f64::NAN.partial_rcmp(2.0..3.0).ordering(), None);
/// ```
fn partial_rcmp<R: RangeBounds<Self>, B: BorrowRange<Self, R>>(
&self,
Expand Down
Loading