usize_bounds/
lib.rs

1use std::ops::{
2    Bound::{Excluded, Included, Unbounded},
3    Range, RangeBounds,
4};
5
6#[derive(Debug)]
7pub enum UsizeOob {
8    StartIndexLen(usize, usize, usize),
9    EndIndexLen(usize, usize, usize),
10    IndexOrder(usize, usize, usize),
11}
12
13impl ToString for UsizeOob {
14    fn to_string(&self) -> String {
15        match self {
16            UsizeOob::StartIndexLen(start, _, len) => {
17                format!(
18                    "range start index {start} out of range for slice of length {len}"
19                )
20            }
21            UsizeOob::EndIndexLen(_, end, len) => {
22                format!(
23                    "range end index {end} out of range for slice of length {len}"
24                )
25            }
26            UsizeOob::IndexOrder(start, end, _) => {
27                format!("slice index starts at {start} but ends at {end}")
28            }
29        }
30    }
31}
32
33impl UsizeOob {
34    pub fn resolve_bounds(&self) -> Range<usize> {
35        let (&start, &end, &len) = match self {
36            UsizeOob::StartIndexLen(start, end, len)
37            | UsizeOob::EndIndexLen(start, end, len)
38            | UsizeOob::IndexOrder(start, end, len) => (start, end, len),
39        };
40        let end = end.min(len);
41        let start = start.min(end);
42        start..end
43    }
44}
45
46pub trait UsizeBounds {
47    fn to_range(&self, len: usize) -> Range<usize>;
48    fn checked_to_range(&self, len: usize) -> Result<Range<usize>, UsizeOob>;
49}
50
51impl<R: RangeBounds<usize>> UsizeBounds for R {
52    fn to_range(&self, len: usize) -> Range<usize> {
53        match self.checked_to_range(len) {
54            Ok(o) => o,
55            Err(e) => panic!("{}", e.to_string()),
56        }
57    }
58
59    fn checked_to_range(&self, len: usize) -> Result<Range<usize>, UsizeOob> {
60        let start = match self.start_bound() {
61            Included(&s) => s,
62            Excluded(&s) => s + 1,
63            Unbounded => 0,
64        };
65        let end = match self.end_bound() {
66            Included(&e) => e + 1,
67            Excluded(&e) => e,
68            Unbounded => len,
69        };
70
71        if start > len {
72            Err(UsizeOob::StartIndexLen(start, end, len))
73        } else if end > len {
74            Err(UsizeOob::EndIndexLen(start, end, len))
75        } else if start > end {
76            Err(UsizeOob::IndexOrder(start, end, len))
77        } else {
78            // start <= end <= len
79            Ok(start..end)
80        }
81    }
82}