bitcoin_internals/
array.rs

1//! Contains extensions related to arrays.
2
3/// Extension trait for arrays.
4pub trait ArrayExt {
5    /// The item type the array is storing.
6    type Item;
7
8    /// Just like the slicing operation, this returns an array `LEN` items long at position
9    /// `OFFSET`.
10    ///
11    /// The correctness of this operation is compile-time checked.
12    ///
13    /// Note that unlike slicing where the second number is the end index, here the second number
14    /// is array length!
15    fn sub_array<const OFFSET: usize, const LEN: usize>(&self) -> &[Self::Item; LEN];
16
17    /// Returns an item at given statically-known index.
18    ///
19    /// This is just like normal indexing except the check happens at compile time.
20    fn get_static<const INDEX: usize>(&self) -> &Self::Item { &self.sub_array::<INDEX, 1>()[0] }
21
22    /// Returns the first item in an array.
23    ///
24    /// Fails to compile if the array is empty.
25    ///
26    /// Note that this method's name intentionally shadows the `std`'s `first` method which
27    /// returns `Option`. The rationale is that given the known length of the array, we always know
28    /// that this will not return `None` so trying to keep the `std` method around is pointless.
29    /// Importing the trait will also cause compile failures - that's also intentional to expose
30    /// the places where useless checks are made.
31    fn first(&self) -> &Self::Item { self.get_static::<0>() }
32
33    /// Splits the array into two, non-overlapping smaller arrays covering the entire range.
34    ///
35    /// This is almost equivalent to just calling [`sub_array`](Self::sub_array) twice, except it also
36    /// checks that the arrays don't overlap and that they cover the full range. This is very useful
37    /// for demonstrating correctness, especially when chained. Using this technique even revealed
38    /// a bug in the past. ([#4195](https://github.com/rust-bitcoin/rust-bitcoin/issues/4195))
39    fn split_array<const LEFT: usize, const RIGHT: usize>(
40        &self,
41    ) -> (&[Self::Item; LEFT], &[Self::Item; RIGHT]);
42
43    /// Splits the array into the first element and the remaining, one element shorter, array.
44    ///
45    /// Fails to compile if the array is empty.
46    ///
47    /// Note that this method's name intentionally shadows the `std`'s `split_first` method which
48    /// returns `Option`. The rationale is that given the known length of the array, we always know
49    /// that this will not return `None` so trying to keep the `std` method around is pointless.
50    /// Importing the trait will also cause compile failures - that's also intentional to expose
51    /// the places where useless checks are made.
52    fn split_first<const RIGHT: usize>(&self) -> (&Self::Item, &[Self::Item; RIGHT]) {
53        let (first, remaining) = self.split_array::<1, RIGHT>();
54        (&first[0], remaining)
55    }
56
57    /// Splits the array into the last element and the remaining, one element shorter, array.
58    ///
59    /// Fails to compile if the array is empty.
60    ///
61    /// Note that this method's name intentionally shadows the `std`'s `split_last` method which
62    /// returns `Option`. The rationale is that given the known length of the array, we always know
63    /// that this will not return `None` so trying to keep the `std` method around is pointless.
64    /// Importing the trait will also cause compile failures - that's also intentional to expose
65    /// the places where useless checks are made.
66    ///
67    /// The returned tuple is also reversed just as `std` for consistency and simpler diffs when
68    /// migrating.
69    fn split_last<const LEFT: usize>(&self) -> (&Self::Item, &[Self::Item; LEFT]) {
70        let (remaining, last) = self.split_array::<LEFT, 1>();
71        (&last[0], remaining)
72    }
73}
74
75impl<const N: usize, T> ArrayExt for [T; N] {
76    type Item = T;
77
78    fn sub_array<const OFFSET: usize, const LEN: usize>(&self) -> &[Self::Item; LEN] {
79        #[allow(clippy::let_unit_value)]
80        let _ = Hack::<N, OFFSET, LEN>::IS_VALID_RANGE;
81
82        self[OFFSET..(OFFSET + LEN)].try_into().expect("this is also compiler-checked above")
83    }
84
85    fn split_array<const LEFT: usize, const RIGHT: usize>(
86        &self,
87    ) -> (&[Self::Item; LEFT], &[Self::Item; RIGHT]) {
88        #[allow(clippy::let_unit_value)]
89        let _ = Hack2::<N, LEFT, RIGHT>::IS_FULL_RANGE;
90
91        (self.sub_array::<0, LEFT>(), self.sub_array::<LEFT, RIGHT>())
92    }
93}
94
95struct Hack<const N: usize, const OFFSET: usize, const LEN: usize>;
96
97impl<const N: usize, const OFFSET: usize, const LEN: usize> Hack<N, OFFSET, LEN> {
98    const IS_VALID_RANGE: () = assert!(OFFSET + LEN <= N);
99}
100
101struct Hack2<const N: usize, const LEFT: usize, const RIGHT: usize>;
102
103impl<const N: usize, const LEFT: usize, const RIGHT: usize> Hack2<N, LEFT, RIGHT> {
104    const IS_FULL_RANGE: () = assert!(LEFT + RIGHT == N);
105}