use std::convert::TryInto;
pub trait SliceExt {
type Item;
fn bitcoin_as_chunks<const N: usize>(&self) -> (&[[Self::Item; N]], &[Self::Item]);
fn bitcoin_as_chunks_mut<const N: usize>(
&mut self,
) -> (&mut [[Self::Item; N]], &mut [Self::Item]);
fn get_array<const ARRAY_LEN: usize>(&self, offset: usize) -> Option<&[Self::Item; ARRAY_LEN]>;
#[allow(clippy::type_complexity)] fn split_first_chunk<const ARRAY_LEN: usize>(
&self,
) -> Option<(&[Self::Item; ARRAY_LEN], &[Self::Item])>;
#[allow(clippy::type_complexity)] fn split_last_chunk<const ARRAY_LEN: usize>(
&self,
) -> Option<(&[Self::Item], &[Self::Item; ARRAY_LEN])>;
}
impl<T> SliceExt for [T] {
type Item = T;
fn bitcoin_as_chunks<const N: usize>(&self) -> (&[[Self::Item; N]], &[Self::Item]) {
#[allow(clippy::let_unit_value)]
let () = Hack::<N>::IS_NONZERO;
let chunks_count = self.len() / N;
let total_left_len = chunks_count * N;
let (left, right) = self.split_at(total_left_len);
let left = unsafe {
core::slice::from_raw_parts(left.as_ptr().cast::<[Self::Item; N]>(), chunks_count)
};
(left, right)
}
fn bitcoin_as_chunks_mut<const N: usize>(
&mut self,
) -> (&mut [[Self::Item; N]], &mut [Self::Item]) {
#[allow(clippy::let_unit_value)]
let () = Hack::<N>::IS_NONZERO;
let chunks_count = self.len() / N;
let total_left_len = chunks_count * N;
let (left, right) = self.split_at_mut(total_left_len);
let left = unsafe {
core::slice::from_raw_parts_mut(
left.as_mut_ptr().cast::<[Self::Item; N]>(),
chunks_count,
)
};
(left, right)
}
fn get_array<const ARRAY_LEN: usize>(&self, offset: usize) -> Option<&[Self::Item; ARRAY_LEN]> {
self.get(offset..(offset + ARRAY_LEN)).map(|slice| {
slice
.try_into()
.expect("the arguments to `get` evaluate to the same length the return type uses")
})
}
fn split_first_chunk<const ARRAY_LEN: usize>(
&self,
) -> Option<(&[Self::Item; ARRAY_LEN], &[Self::Item])> {
if self.len() < ARRAY_LEN {
return None;
}
let (first, remainder) = self.split_at(ARRAY_LEN);
Some((first.try_into().expect("we're passing `ARRAY_LEN` to `split_at` above"), remainder))
}
fn split_last_chunk<const ARRAY_LEN: usize>(
&self,
) -> Option<(&[Self::Item], &[Self::Item; ARRAY_LEN])> {
if self.len() < ARRAY_LEN {
return None;
}
let (remainder, last) = self.split_at(self.len() - ARRAY_LEN);
Some((
remainder,
last.try_into().expect("we're passing `self.len() - ARRAY_LEN` to `split_at` above"),
))
}
}
struct Hack<const N: usize>;
impl<const N: usize> Hack<N> {
const IS_NONZERO: () = {
assert!(N != 0);
};
}
#[cfg(test)]
mod tests {
use super::SliceExt;
const EMPTY: &[i32] = &[];
#[test]
fn one_to_one() {
let slice = [1];
let (left, right) = slice.bitcoin_as_chunks::<1>();
assert_eq!(left, &[[1]]);
assert_eq!(right, EMPTY);
}
#[test]
fn one_to_two() {
const EMPTY_LEFT: &[[i32; 2]] = &[];
let slice = [1i32];
let (left, right) = slice.bitcoin_as_chunks::<2>();
assert_eq!(left, EMPTY_LEFT);
assert_eq!(right, &[1]);
}
#[test]
fn two_to_one() {
let slice = [1, 2];
let (left, right) = slice.bitcoin_as_chunks::<1>();
assert_eq!(left, &[[1], [2]]);
assert_eq!(right, EMPTY);
}
#[test]
fn two_to_two() {
let slice = [1, 2];
let (left, right) = slice.bitcoin_as_chunks::<2>();
assert_eq!(left, &[[1, 2]]);
assert_eq!(right, EMPTY);
}
#[test]
fn three_to_two() {
let slice = [1, 2, 3];
let (left, right) = slice.bitcoin_as_chunks::<2>();
assert_eq!(left, &[[1, 2]]);
assert_eq!(right, &[3]);
}
}