use std::ops::{Bound, Deref, RangeBounds};
use std::ptr::NonNull;
use std::{fmt, slice};
use crate::mem::{BlockRef, BlockSize};
#[derive(Clone)]
pub(crate) struct Span {
block_ref: BlockRef,
start: NonNull<u8>,
len: BlockSize,
}
impl Span {
pub(crate) const unsafe fn new(start: NonNull<u8>, len: BlockSize, block_ref: BlockRef) -> Self {
Self { block_ref, start, len }
}
#[must_use]
pub const fn len(&self) -> BlockSize {
self.len
}
#[must_use]
pub(crate) const fn is_empty(&self) -> bool {
self.len() == 0
}
pub(crate) fn slice<R>(&self, range: R) -> Self
where
R: RangeBounds<BlockSize>,
{
self.slice_checked(range).expect("provided range is out of span bounds")
}
pub(crate) fn slice_checked<R>(&self, range: R) -> Option<Self>
where
R: RangeBounds<BlockSize>,
{
let bytes_until_range = match range.start_bound() {
Bound::Included(&x) => x,
Bound::Excluded(&x) => x.checked_add(1)?,
Bound::Unbounded => 0,
};
let bytes_in_range = match range.end_bound() {
Bound::Included(&x) => x.checked_add(1)?.checked_sub(bytes_until_range)?,
Bound::Excluded(&x) => x.checked_sub(bytes_until_range)?,
Bound::Unbounded => self.len().checked_sub(bytes_until_range)?,
};
let required_len = bytes_until_range.wrapping_add(bytes_in_range);
if required_len > self.len {
return None;
}
Some(Self {
block_ref: Clone::clone(&self.block_ref),
start: unsafe { self.start.add(bytes_until_range as usize) },
len: bytes_in_range,
})
}
pub(crate) const fn block_ref(&self) -> &BlockRef {
&self.block_ref
}
pub(crate) unsafe fn advance(&mut self, len: usize) {
#[expect(clippy::cast_possible_truncation, reason = "guaranteed by safety requirements")]
let len_bs = len as BlockSize;
self.len = self.len.wrapping_sub(len_bs);
self.start = unsafe { self.start.add(len) };
}
#[cfg(test)]
#[expect(clippy::cast_possible_truncation, reason = "test code, relax")]
pub(crate) fn get_array<const N: usize>(&mut self) -> [u8; N] {
assert!(self.len() >= N as BlockSize, "out of bounds read");
let mut array = [0_u8; N];
let src = unsafe { slice::from_raw_parts(self.start.as_ptr(), N) };
array.copy_from_slice(src);
unsafe {
self.advance(N);
}
array
}
}
impl Deref for Span {
type Target = [u8];
fn deref(&self) -> &Self::Target {
unsafe { slice::from_raw_parts(self.start.as_ptr(), self.len as usize) }
}
}
impl AsRef<[u8]> for Span {
fn as_ref(&self) -> &[u8] {
self
}
}
impl fmt::Debug for Span {
#[cfg_attr(coverage_nightly, coverage(off))] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut debug_struct = f.debug_struct("Span");
debug_struct.field("start", &self.start).field("len", &self.len);
if self.len >= 4 {
let byte1 = self[0];
let byte2 = self[1];
let byte3 = self[2];
let byte4 = self[3];
debug_struct.field("first_four_bytes", &format!("{byte1:02x}{byte2:02x}{byte3:02x}{byte4:02x}"));
}
debug_struct.field("block_ref", &self.block_ref).finish()
}
}
unsafe impl Send for Span {}
unsafe impl Sync for Span {}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use std::num::NonZero;
use new_zealand::nz;
use static_assertions::assert_impl_all;
use super::*;
use crate::mem::testing::std_alloc_block;
assert_impl_all!(Span: Send, Sync);
#[test]
fn smoke_test() {
let mut builder = std_alloc_block::allocate(nz!(10)).into_span_builder();
builder.put_slice(&1234_u64.to_ne_bytes());
builder.put_slice(&16_u16.to_ne_bytes());
let mut span = builder.consume(nz!(10));
assert_eq!(0, builder.remaining_capacity());
assert_eq!(span.len(), 10);
assert!(!span.is_empty());
assert_eq!(10, span.as_ref().len());
assert_eq!(u64::from_ne_bytes(span.get_array()), 1234);
assert_eq!(u16::from_ne_bytes(span.get_array()), 16);
assert_eq!(0, span.len());
assert!(span.is_empty());
}
#[test]
fn slice() {
let mut builder = std_alloc_block::allocate(nz!(10)).into_span_builder();
builder.put_slice(&1234_u64.to_ne_bytes());
builder.put_slice(&16_u16.to_ne_bytes());
let mut span1 = builder.consume(nz!(10));
let mut span2 = span1.slice(0..10);
let mut span3 = span1.slice(8..10);
let span4 = span1.slice(10..10);
assert!(span1.slice_checked(0..11).is_none());
assert_eq!(span1.len(), 10);
assert_eq!(span2.len(), 10);
assert_eq!(span3.len(), 2);
assert_eq!(span4.len(), 0);
assert_eq!(u64::from_ne_bytes(span1.get_array()), 1234);
assert_eq!(u16::from_ne_bytes(span1.get_array()), 16);
assert_eq!(u64::from_ne_bytes(span2.get_array()), 1234);
assert_eq!(u16::from_ne_bytes(span2.get_array()), 16);
assert_eq!(u16::from_ne_bytes(span3.get_array()), 16);
}
#[test]
fn size_change_detector() {
assert_eq!(size_of::<Span>(), 32);
}
#[test]
fn slice_indexing_kinds() {
let mut buf = std_alloc_block::allocate(nz!(10)).into_span_builder();
buf.put_slice(&[0, 1, 2, 3, 4, 5]);
let span = buf.consume(NonZero::new(buf.len()).unwrap());
let mut middle_four = span.slice(1..5);
assert_eq!(4, middle_four.len());
assert_eq!(1, u8::from_ne_bytes(middle_four.get_array()));
assert_eq!(2, u8::from_ne_bytes(middle_four.get_array()));
assert_eq!(3, u8::from_ne_bytes(middle_four.get_array()));
assert_eq!(4, u8::from_ne_bytes(middle_four.get_array()));
let mut middle_four = span.slice(1..=4);
assert_eq!(4, middle_four.len());
assert_eq!(1, u8::from_ne_bytes(middle_four.get_array()));
assert_eq!(2, u8::from_ne_bytes(middle_four.get_array()));
assert_eq!(3, u8::from_ne_bytes(middle_four.get_array()));
assert_eq!(4, u8::from_ne_bytes(middle_four.get_array()));
let mut last_two = span.slice(4..);
assert_eq!(2, last_two.len());
assert_eq!(4, u8::from_ne_bytes(last_two.get_array()));
assert_eq!(5, u8::from_ne_bytes(last_two.get_array()));
let mut first_two = span.slice(..2);
assert_eq!(2, first_two.len());
assert_eq!(0, u8::from_ne_bytes(first_two.get_array()));
assert_eq!(1, u8::from_ne_bytes(first_two.get_array()));
let mut first_two = span.slice(..=1);
assert_eq!(2, first_two.len());
assert_eq!(0, u8::from_ne_bytes(first_two.get_array()));
assert_eq!(1, u8::from_ne_bytes(first_two.get_array()));
}
#[test]
fn slice_checked_with_excluded_start_bound() {
use std::ops::Bound;
let mut sub = std_alloc_block::allocate(nz!(100)).into_span_builder();
sub.put_slice(&[0, 1, 2, 3, 4, 5, 6, 7, 8]);
let span = sub.consume(NonZero::new(sub.len()).unwrap());
let sliced = span.slice_checked((Bound::Excluded(1), Bound::Excluded(5)));
assert!(sliced.is_some());
let mut sliced = sliced.unwrap();
assert_eq!(3, sliced.len());
assert_eq!(2, u8::from_ne_bytes(sliced.get_array()));
assert_eq!(3, u8::from_ne_bytes(sliced.get_array()));
assert_eq!(4, u8::from_ne_bytes(sliced.get_array()));
let sliced = span.slice_checked((Bound::Excluded(8), Bound::Unbounded));
assert!(sliced.is_some());
assert_eq!(0, sliced.unwrap().len());
}
}