to_offset/
lib.rs

1/// Trait to allow the main signed and unsigned integer types
2/// where negative values are treated as offsets from the end, defined by length
3/// -1 == length -1, 0 == start, 1 == second position
4/// If the offset underflows, it returns 0.
5/// If it overflows, it returns the last index for arrays or the length for integer types.
6pub trait ToOffset where Self:PartialOrd {
7    fn to_offset(self, length: usize) -> usize;
8}
9
10  
11fn offset_wihin_range(offset: usize, length: usize) -> usize {
12if offset > length {
13    length
14} else {
15    offset
16}
17}
18
19/// Macro to implement the above for signed types
20macro_rules! impl_indexable_signed_to_offset {
21    ($t:ty) => {
22        impl ToOffset for $t {
23        fn to_offset(self, length: usize) -> usize {
24            let offset = if self < 0 {
25                length.saturating_sub(self.abs() as usize)
26            } else {
27                self as usize
28            };
29            offset_wihin_range(offset, length)
30        }
31        }
32    };
33}
34
35/// Macro to implement the above unsigned types
36macro_rules! impl_indexable_unsigned_to_offset {
37    ($t:ty) => {
38        impl ToOffset for $t {
39        fn to_offset(self, length: usize) -> usize {
40            offset_wihin_range(self as usize, length)
41        }
42        }
43    };
44}
45
46// Implement for i32
47impl_indexable_signed_to_offset!(i32);
48
49// Implement for i64
50impl_indexable_signed_to_offset!(i64);
51
52// Implement for u8
53impl_indexable_unsigned_to_offset!(u8);
54
55// Implement for u16
56impl_indexable_unsigned_to_offset!(u16);
57
58// Implement for u32
59impl_indexable_unsigned_to_offset!(u32);
60
61// Implement for u64
62impl_indexable_unsigned_to_offset!(u64);
63
64// Implement for usize
65impl_indexable_unsigned_to_offset!(usize);
66 
67
68/// Provide from_offset convenience method that allows negative offsets 
69/// from the end of an array/vector and never extends beyond the array/vector bounds
70/// It returns Option<&T> in keeping with the default get() method
71/// and because the array or vector may still be empty
72pub trait FromOffset<T> where T:Sized {
73    fn from_offset<U: ToOffset>(&self, relative_index: U) -> Option<&T>;
74}
75
76impl<T> FromOffset<T> for [T] {
77    fn from_offset<U: ToOffset>(&self, relative_index: U) -> Option<&T> {
78        let length = self.len();
79        if length < 1 {
80            return None;
81        }
82        let target_index = relative_index.to_offset(length);
83        let index = if target_index < length {
84            target_index
85        } else {
86            target_index - 1
87        };
88        self.get(index)
89    }
90}
91
92 
93#[cfg(test)]
94mod tests {
95    use super::*;
96
97    #[test]
98    fn test_end_offset() {
99        let integer_array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
100
101        let penultimate_index = (-2).to_offset(integer_array.len());
102
103        assert_eq!(penultimate_index, 10);
104
105        let penultimate_value = integer_array[penultimate_index];
106
107        assert_eq!(penultimate_value, 11);
108
109        let third_from_last = integer_array.from_offset(-3);
110
111        assert_eq!(*third_from_last.unwrap(), 10);
112    }
113
114    #[test]
115    fn test_array_overflow() {
116        let integer_array = [1, 2, 3, 4, 5, 6];
117
118        let distant_element_index = 1_000_000.to_offset(integer_array.len());
119
120        // should be the last element
121        assert_eq!(distant_element_index, 6); 
122
123        let twentieth_from_the_end = integer_array.from_offset(-20);
124
125        // should be the first [0] element with a vakue of 1 as it cannot extend before the start
126        assert_eq!(twentieth_from_the_end.unwrap().to_owned(), 1); 
127
128    }
129}