offset_views/
with_offset.rs

1/// Functionality to add an offset to a value and convert it.
2///
3/// # Examples
4///
5/// ```rust
6/// use offset_views::with_offset::WithOffset;
7/// assert_eq!(3.with_offset(5), 8);
8/// assert_eq!((3..8).with_offset(-3), 0..5);
9/// ```
10pub trait WithOffset<O> {
11    type Output;
12    /// Adds an offset to a value and tries to convert the result to a suitable output type.
13    ///
14    /// This method may panic if it is not possible to represent the result in the output type.
15    fn with_offset(&self, offset: O) -> Self::Output;
16}
17
18impl WithOffset<isize> for isize {
19    type Output = usize;
20    fn with_offset(&self, offset: isize) -> usize {
21        (self + offset)
22            .try_into()
23            .expect("cannot convert to `usize`")
24    }
25}
26
27use std::ops::{Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive};
28
29impl<I> WithOffset<isize> for Range<I>
30where
31    I: WithOffset<isize, Output = usize>,
32{
33    type Output = Range<usize>;
34    fn with_offset(&self, offset: isize) -> Range<usize> {
35        self.start.with_offset(offset)..self.end.with_offset(offset)
36    }
37}
38
39impl<I> WithOffset<isize> for RangeInclusive<I>
40where
41    I: WithOffset<isize, Output = usize>,
42{
43    type Output = RangeInclusive<usize>;
44    fn with_offset(&self, offset: isize) -> RangeInclusive<usize> {
45        self.start().with_offset(offset)..=self.end().with_offset(offset)
46    }
47}
48
49impl<I> WithOffset<isize> for RangeFrom<I>
50where
51    I: WithOffset<isize, Output = usize>,
52{
53    type Output = RangeFrom<usize>;
54    fn with_offset(&self, offset: isize) -> RangeFrom<usize> {
55        self.start.with_offset(offset)..
56    }
57}
58
59impl<I> WithOffset<isize> for RangeTo<I>
60where
61    I: WithOffset<isize, Output = usize>,
62{
63    type Output = RangeTo<usize>;
64    fn with_offset(&self, offset: isize) -> RangeTo<usize> {
65        ..self.end.with_offset(offset)
66    }
67}
68
69impl<I> WithOffset<isize> for RangeToInclusive<I>
70where
71    I: WithOffset<isize, Output = usize>,
72{
73    type Output = RangeToInclusive<usize>;
74    fn with_offset(&self, offset: isize) -> RangeToInclusive<usize> {
75        ..=self.end.with_offset(offset)
76    }
77}
78
79impl WithOffset<isize> for RangeFull {
80    type Output = RangeFull;
81    fn with_offset(&self, _offset: isize) -> RangeFull {
82        RangeFull
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89    use cool_asserts::assert_panics;
90
91    #[test]
92    fn isize() {
93        assert_eq!(0.with_offset(5), 5);
94        assert_panics!(0.with_offset(-5), includes("cannot convert to `usize`"));
95        assert_eq!(3.with_offset(3), 6);
96        assert_eq!(3.with_offset(-3), 0);
97        assert_panics!(3.with_offset(-5), includes("cannot convert to `usize`"));
98    }
99
100    #[test]
101    fn range() {
102        assert_eq!((0..10).with_offset(3), 3..13);
103        assert_eq!((3..13).with_offset(-3), 0..10);
104        assert_panics!(
105            (3..13).with_offset(-5),
106            includes("cannot convert to `usize`")
107        );
108    }
109
110    #[test]
111    fn range_inclusive() {
112        assert_eq!((0..=10).with_offset(3), 3..=13);
113        assert_eq!((3..=13).with_offset(-3), 0..=10);
114        assert_panics!(
115            (3..=13).with_offset(-5),
116            includes("cannot convert to `usize`")
117        );
118    }
119
120    #[test]
121    fn range_from() {
122        assert_eq!((0..).with_offset(3), 3..);
123        assert_eq!((3..).with_offset(-3), 0..);
124        assert_panics!((3..).with_offset(-5), includes("cannot convert to `usize`"));
125    }
126
127    #[test]
128    fn range_to() {
129        assert_eq!((..10).with_offset(3), ..13);
130        assert_eq!((..13).with_offset(-3), ..10);
131        assert_eq!((..13).with_offset(-5), (..8));
132    }
133
134    #[test]
135    fn range_to_inclusive() {
136        assert_eq!((..=10).with_offset(3), ..=13);
137        assert_eq!((..=13).with_offset(-3), ..=10);
138        assert_eq!((..=13).with_offset(-5), (..=8));
139    }
140
141    #[test]
142    fn range_full() {
143        assert_eq!(RangeFull.with_offset(5), RangeFull);
144        assert_eq!(RangeFull.with_offset(-5), RangeFull);
145    }
146}