any_range/
lib.rs

1//! any-range
2//! =========
3//! [![crates.io version](https://img.shields.io/crates/v/any-range.svg)](https://crates.io/crates/any-range)
4//! [![license: Apache 2.0](https://gitlab.com/leonhard-llc/ops/-/raw/main/license-apache-2.0.svg)](https://gitlab.com/leonhard-llc/ops/-/raw/main/any-range/LICENSE)
5//! [![unsafe forbidden](https://gitlab.com/leonhard-llc/ops/-/raw/main/unsafe-forbidden.svg)](https://github.com/rust-secure-code/safety-dance/)
6//! [![pipeline status](https://gitlab.com/leonhard-llc/ops/badges/main/pipeline.svg)](https://gitlab.com/leonhard-llc/ops/-/pipelines)
7//!
8//! `AnyRange<T>` enum can hold any `Range*<T>` type.
9//!
10//! # Use Cases
11//! - Store any kind of range in a struct without adding a type parameter
12//!
13//! # Features
14//! - `no_std`, depends only on `core`
15//! - `forbid(unsafe_code)`
16//! - 100% test coverage
17//!
18//! # Limitations
19//! - Uses more bytes than a plain `Range<T>`.
20//!   The alignment of `T` determines how many extra bytes the enum uses.
21//!
22//! # Alternatives
23//! - [`anyrange`](https://crates.io/crates/anyrange)
24//!   - Should be called `ToRange`
25//!   - Doesn't support `RangeInclusive` or `RangeToInclusive`
26//!   - Unmaintained
27//!
28//! # Example
29//! ```
30//! use any_range::AnyRange;
31//! let r = AnyRange::from(3..5);
32//! assert!(r.contains(&3));
33//! assert_eq!("de", &"abcdefg"[r.bounds()]);
34//! ```
35//!
36//! # Cargo Geiger Safety Report
37//! # Changelog
38//! - v0.1.4 - Add `bounds` method for slicing
39//! - v0.1.3 - Implement `Hash`, `PartialOrd`, `Ord`
40//! - v0.1.2 - Increase test coverage
41//! - v0.1.1 - Update docs
42//! - v0.1.0 - Initial version
43#![forbid(unsafe_code)]
44
45use core::fmt::Debug;
46use core::ops::{Bound, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive};
47use std::cmp::Ordering;
48use std::ops::RangeBounds;
49
50/// An enum that can hold any Range* type.
51///
52/// # Example
53/// ```
54/// use any_range::AnyRange;
55/// let r = AnyRange::from(3..5);
56/// assert!(r.contains(&3));
57/// assert_eq!("de", &"abcdefg"[r.bounds()]);
58/// ```
59#[derive(Clone, PartialEq, Eq, Hash)]
60pub enum AnyRange<T: Clone + PartialOrd + PartialEq> {
61    Range(Range<T>),
62    RangeFrom(RangeFrom<T>),
63    RangeFull(RangeFull),
64    RangeInclusive(RangeInclusive<T>),
65    RangeTo(RangeTo<T>),
66    RangeToInclusive(RangeToInclusive<T>),
67}
68impl<T: Clone + PartialOrd + PartialEq> AnyRange<T> {
69    /// Returns the bounds, useful for slicing.
70    /// # Example
71    /// ```
72    /// use any_range::AnyRange;
73    /// let r = AnyRange::from(1..3usize);
74    /// assert_eq!("bc", &"abcd"[r.bounds()]);
75    /// ```
76    #[must_use]
77    pub fn bounds(&self) -> (Bound<T>, Bound<T>) {
78        (self.start_bound().cloned(), self.end_bound().cloned())
79    }
80    /// Returns true if item is contained in the range.
81    pub fn contains(&self, value: &T) -> bool {
82        RangeBounds::contains(self, value)
83    }
84    /// Returns the start value as a Bound.
85    pub fn start_bound(&self) -> Bound<&T> {
86        RangeBounds::start_bound(self)
87    }
88    /// Returns the end value as a Bound.
89    pub fn end_bound(&self) -> Bound<&T> {
90        RangeBounds::end_bound(self)
91    }
92    fn order(&self) -> u8 {
93        match self {
94            Self::Range(_) => 0,
95            Self::RangeFrom(_) => 1,
96            Self::RangeFull(_) => 2,
97            Self::RangeInclusive(_) => 3,
98            Self::RangeTo(_) => 4,
99            Self::RangeToInclusive(_) => 5,
100        }
101    }
102}
103impl<T: Clone + PartialOrd + PartialEq> RangeBounds<T> for AnyRange<T> {
104    fn start_bound(&self) -> Bound<&T> {
105        match self {
106            Self::Range(r) => r.start_bound(),
107            Self::RangeFrom(r) => r.start_bound(),
108            Self::RangeFull(r) => r.start_bound(),
109            Self::RangeInclusive(r) => r.start_bound(),
110            Self::RangeTo(r) => r.start_bound(),
111            Self::RangeToInclusive(r) => r.start_bound(),
112        }
113    }
114    fn end_bound(&self) -> Bound<&T> {
115        match self {
116            Self::Range(r) => r.end_bound(),
117            Self::RangeFrom(r) => r.end_bound(),
118            Self::RangeFull(r) => r.end_bound(),
119            Self::RangeInclusive(r) => r.end_bound(),
120            Self::RangeTo(r) => r.end_bound(),
121            Self::RangeToInclusive(r) => r.end_bound(),
122        }
123    }
124}
125impl<T: Clone + PartialOrd + PartialEq> From<Range<T>> for AnyRange<T> {
126    fn from(r: Range<T>) -> Self {
127        Self::Range(r)
128    }
129}
130impl<T: Clone + PartialOrd + PartialEq> From<RangeFrom<T>> for AnyRange<T> {
131    fn from(r: RangeFrom<T>) -> Self {
132        Self::RangeFrom(r)
133    }
134}
135impl<T: Clone + PartialOrd + PartialEq> From<RangeFull> for AnyRange<T> {
136    fn from(r: RangeFull) -> Self {
137        Self::RangeFull(r)
138    }
139}
140impl<T: Clone + PartialOrd + PartialEq> From<RangeInclusive<T>> for AnyRange<T> {
141    fn from(r: RangeInclusive<T>) -> Self {
142        Self::RangeInclusive(r)
143    }
144}
145impl<T: Clone + PartialOrd + PartialEq> From<RangeTo<T>> for AnyRange<T> {
146    fn from(r: RangeTo<T>) -> Self {
147        Self::RangeTo(r)
148    }
149}
150impl<T: Clone + PartialOrd + PartialEq> From<RangeToInclusive<T>> for AnyRange<T> {
151    fn from(r: RangeToInclusive<T>) -> Self {
152        Self::RangeToInclusive(r)
153    }
154}
155impl<T: Clone + PartialOrd + PartialEq + Debug> Debug for AnyRange<T> {
156    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
157        match self {
158            AnyRange::Range(r) => write!(f, "AnyRange({r:?})"),
159            AnyRange::RangeFrom(r) => write!(f, "AnyRange({r:?})"),
160            AnyRange::RangeFull(r) => write!(f, "AnyRange({r:?})"),
161            AnyRange::RangeInclusive(r) => write!(f, "AnyRange({r:?})"),
162            AnyRange::RangeTo(r) => write!(f, "AnyRange({r:?})"),
163            AnyRange::RangeToInclusive(r) => write!(f, "AnyRange({r:?})"),
164        }
165    }
166}
167impl<T: Clone + PartialOrd + PartialEq + Debug> PartialOrd for AnyRange<T> {
168    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
169        match (self, other) {
170            (AnyRange::Range(a), AnyRange::Range(b)) if a.start == b.start => {
171                a.end.partial_cmp(&b.end)
172            }
173            (AnyRange::Range(a), AnyRange::Range(b)) => a.start.partial_cmp(&b.start),
174            (AnyRange::RangeFrom(a), AnyRange::RangeFrom(b)) => a.start.partial_cmp(&b.start),
175            (AnyRange::RangeFull(_), AnyRange::RangeFull(_)) => Some(Ordering::Equal),
176            (AnyRange::RangeInclusive(a), AnyRange::RangeInclusive(b))
177                if a.start() == b.start() =>
178            {
179                a.end().partial_cmp(b.end())
180            }
181            (AnyRange::RangeInclusive(a), AnyRange::RangeInclusive(b)) => {
182                a.start().partial_cmp(b.start())
183            }
184            (AnyRange::RangeTo(a), AnyRange::RangeTo(b)) => a.end.partial_cmp(&b.end),
185            (AnyRange::RangeToInclusive(a), AnyRange::RangeToInclusive(b)) => {
186                a.end.partial_cmp(&b.end)
187            }
188            (a, b) => a.order().partial_cmp(&b.order()),
189        }
190    }
191}
192impl<T: Clone + PartialOrd + PartialEq + Eq + Debug> Ord for AnyRange<T> {
193    fn cmp(&self, other: &Self) -> Ordering {
194        PartialOrd::partial_cmp(self, other).unwrap()
195    }
196}