bitcoin_internals/
slice.rs

1//! Contains extensions related to slices.
2
3/// Extension trait for slice.
4pub trait SliceExt {
5    /// The item type the slice is storing.
6    type Item;
7
8    /// Splits up the slice into a slice of arrays and a remainder.
9    ///
10    /// Note that `N` must not be zero:
11    ///
12    /// ```ignore
13    /// # use bitcoin_internals::slice::SliceExt;
14    /// let slice = [1, 2, 3];
15    /// let _fail = slice.bitcoin_as_chunks::<0>(); // Fails to compile
16    /// ```
17    fn bitcoin_as_chunks<const N: usize>(&self) -> (&[[Self::Item; N]], &[Self::Item]);
18
19    /// Splits up the slice into a slice of arrays and a remainder.
20    ///
21    /// Note that `N` must not be zero:
22    ///
23    /// ```ignore
24    /// # use bitcoin_internals::slice::SliceExt;
25    /// let mut slice = [1, 2, 3];
26    /// let _fail = slice.bitcoin_as_chunks_mut::<0>(); // Fails to compile
27    /// ```
28    fn bitcoin_as_chunks_mut<const N: usize>(
29        &mut self,
30    ) -> (&mut [[Self::Item; N]], &mut [Self::Item]);
31
32    /// Tries to access a sub-array of length `ARRAY_LEN` at the specified `offset`.
33    ///
34    /// Returns `None` in case of out-of-bounds access.
35    fn get_array<const ARRAY_LEN: usize>(&self, offset: usize) -> Option<&[Self::Item; ARRAY_LEN]>;
36
37    /// Splits the slice into an array and remainder if it's long enough.
38    ///
39    /// Returns `None` if the slice is shorter than `ARRAY_LEN`
40    #[allow(clippy::type_complexity)] // it's not really complex and redefining would make it
41                                      // harder to understand
42    fn split_first_chunk<const ARRAY_LEN: usize>(
43        &self,
44    ) -> Option<(&[Self::Item; ARRAY_LEN], &[Self::Item])>;
45
46    /// Splits the slice into a remainder and an array if it's long enough.
47    ///
48    /// Returns `None` if the slice is shorter than `ARRAY_LEN`
49    #[allow(clippy::type_complexity)] // it's not really complex and redefining would make it
50                                      // harder to understand
51    fn split_last_chunk<const ARRAY_LEN: usize>(
52        &self,
53    ) -> Option<(&[Self::Item], &[Self::Item; ARRAY_LEN])>;
54}
55
56impl<T> SliceExt for [T] {
57    type Item = T;
58
59    fn bitcoin_as_chunks<const N: usize>(&self) -> (&[[Self::Item; N]], &[Self::Item]) {
60        #[allow(clippy::let_unit_value)]
61        let _ = Hack::<N>::IS_NONZERO;
62
63        let chunks_count = self.len() / N;
64        let total_left_len = chunks_count * N;
65        let (left, right) = self.split_at(total_left_len);
66        // SAFETY: we've obtained the pointer from a slice that's still live
67        // we're merely casting, so no aliasing issues here
68        // arrays of T have same alignment as T
69        // the resulting slice points within the obtained slice as was computed above
70        let left = unsafe {
71            core::slice::from_raw_parts(left.as_ptr().cast::<[Self::Item; N]>(), chunks_count)
72        };
73        (left, right)
74    }
75
76    fn bitcoin_as_chunks_mut<const N: usize>(
77        &mut self,
78    ) -> (&mut [[Self::Item; N]], &mut [Self::Item]) {
79        #[allow(clippy::let_unit_value)]
80        let _ = Hack::<N>::IS_NONZERO;
81
82        let chunks_count = self.len() / N;
83        let total_left_len = chunks_count * N;
84        let (left, right) = self.split_at_mut(total_left_len);
85        // SAFETY: we've obtained the pointer from a slice that's still live
86        // we're merely casting, so no aliasing issues here
87        // arrays of T have same alignment as T
88        // the resulting slice points within the obtained slice as was computed above
89        let left = unsafe {
90            core::slice::from_raw_parts_mut(
91                left.as_mut_ptr().cast::<[Self::Item; N]>(),
92                chunks_count,
93            )
94        };
95        (left, right)
96    }
97
98    fn get_array<const ARRAY_LEN: usize>(&self, offset: usize) -> Option<&[Self::Item; ARRAY_LEN]> {
99        self.get(offset..(offset + ARRAY_LEN)).map(|slice| {
100            slice
101                .try_into()
102                .expect("the arguments to `get` evaluate to the same length the return type uses")
103        })
104    }
105
106    fn split_first_chunk<const ARRAY_LEN: usize>(
107        &self,
108    ) -> Option<(&[Self::Item; ARRAY_LEN], &[Self::Item])> {
109        if self.len() < ARRAY_LEN {
110            return None;
111        }
112        let (first, remainder) = self.split_at(ARRAY_LEN);
113        Some((first.try_into().expect("we're passing `ARRAY_LEN` to `split_at` above"), remainder))
114    }
115
116    fn split_last_chunk<const ARRAY_LEN: usize>(
117        &self,
118    ) -> Option<(&[Self::Item], &[Self::Item; ARRAY_LEN])> {
119        if self.len() < ARRAY_LEN {
120            return None;
121        }
122        let (remainder, last) = self.split_at(self.len() - ARRAY_LEN);
123        Some((
124            remainder,
125            last.try_into().expect("we're passing `self.len() - ARRAY_LEN` to `split_at` above"),
126        ))
127    }
128}
129
130struct Hack<const N: usize>;
131
132impl<const N: usize> Hack<N> {
133    const IS_NONZERO: () = {
134        assert!(N != 0);
135    };
136}
137
138#[cfg(test)]
139mod tests {
140    use super::SliceExt;
141
142    // some comparisons require type annotations
143    const EMPTY: &[i32] = &[];
144
145    #[test]
146    fn one_to_one() {
147        let slice = [1];
148        let (left, right) = slice.bitcoin_as_chunks::<1>();
149        assert_eq!(left, &[[1]]);
150        assert_eq!(right, EMPTY);
151    }
152
153    #[test]
154    fn one_to_two() {
155        const EMPTY_LEFT: &[[i32; 2]] = &[];
156
157        let slice = [1i32];
158        let (left, right) = slice.bitcoin_as_chunks::<2>();
159        assert_eq!(left, EMPTY_LEFT);
160        assert_eq!(right, &[1]);
161    }
162
163    #[test]
164    fn two_to_one() {
165        let slice = [1, 2];
166        let (left, right) = slice.bitcoin_as_chunks::<1>();
167        assert_eq!(left, &[[1], [2]]);
168        assert_eq!(right, EMPTY);
169    }
170
171    #[test]
172    fn two_to_two() {
173        let slice = [1, 2];
174        let (left, right) = slice.bitcoin_as_chunks::<2>();
175        assert_eq!(left, &[[1, 2]]);
176        assert_eq!(right, EMPTY);
177    }
178
179    #[test]
180    fn three_to_two() {
181        let slice = [1, 2, 3];
182        let (left, right) = slice.bitcoin_as_chunks::<2>();
183        assert_eq!(left, &[[1, 2]]);
184        assert_eq!(right, &[3]);
185    }
186}