use super::*;
use crate::pointer::invariant::{Aligned, Exclusive, Invariants, Safe, Shared};
#[cfg_attr(feature = "derive", doc = "[derive]: zerocopy_derive::SplitAt")]
#[cfg_attr(
not(feature = "derive"),
doc = concat!("[derive]: https://docs.rs/zerocopy/", env!("CARGO_PKG_VERSION"), "/zerocopy/derive.SplitAt.html"),
)]
#[cfg_attr(
not(no_zerocopy_diagnostic_on_unimplemented_1_78_0),
diagnostic::on_unimplemented(note = "Consider adding `#[derive(SplitAt)]` to `{Self}`")
)]
pub unsafe trait SplitAt: KnownLayout<PointerMetadata = usize> {
type Elem;
#[doc(hidden)]
fn only_derive_is_allowed_to_implement_this_trait()
where
Self: Sized;
#[doc = codegen_section!(
header = "h5",
bench = "split_at_unchecked",
format = "coco",
arity = 2,
[
open
@index 1
@title "Unsized"
@variant "dynamic_size"
],
[
@index 2
@title "Dynamically Padded"
@variant "dynamic_padding"
]
)]
#[inline]
#[must_use]
unsafe fn split_at_unchecked(&self, l_len: usize) -> Split<&Self> {
unsafe { Split::<&Self>::new(self, l_len) }
}
#[doc = codegen_section!(
header = "h5",
bench = "split_at",
format = "coco",
arity = 2,
[
open
@index 1
@title "Unsized"
@variant "dynamic_size"
],
[
@index 2
@title "Dynamically Padded"
@variant "dynamic_padding"
]
)]
#[inline]
#[must_use = "has no side effects"]
fn split_at(&self, l_len: usize) -> Option<Split<&Self>> {
MetadataOf::new_in_bounds(self, l_len).map(
#[inline(always)]
|l_len| {
unsafe { Split::new(self, l_len.get()) }
},
)
}
#[doc = codegen_header!("h5", "split_at_mut_unchecked")]
#[inline]
#[must_use]
unsafe fn split_at_mut_unchecked(&mut self, l_len: usize) -> Split<&mut Self> {
unsafe { Split::<&mut Self>::new(self, l_len) }
}
#[doc = codegen_header!("h5", "split_at_mut")]
#[inline]
fn split_at_mut(&mut self, l_len: usize) -> Option<Split<&mut Self>> {
MetadataOf::new_in_bounds(self, l_len).map(
#[inline(always)]
|l_len| {
unsafe { Split::new(self, l_len.get()) }
},
)
}
}
unsafe impl<T> SplitAt for [T] {
type Elem = T;
#[inline]
#[allow(dead_code)]
fn only_derive_is_allowed_to_implement_this_trait()
where
Self: Sized,
{
}
}
unsafe impl<T: ?Sized + SplitAt> SplitAt for ManuallyDrop<T> {
type Elem = T::Elem;
#[inline]
#[allow(dead_code)]
fn only_derive_is_allowed_to_implement_this_trait()
where
Self: Sized,
{
}
}
#[derive(Debug)]
pub struct Split<T> {
source: T,
l_len: usize,
}
impl<T> Split<T> {
#[inline(always)]
unsafe fn new(source: T, l_len: usize) -> Self {
Self { source, l_len }
}
}
impl<'a, T> Split<&'a T>
where
T: ?Sized + SplitAt,
{
#[inline(always)]
fn into_ptr(self) -> Split<Ptr<'a, T, (Shared, Aligned, Safe)>> {
let source = Ptr::from_ref(self.source);
unsafe { Split::new(source, self.l_len) }
}
#[doc = codegen_section!(
header = "h5",
bench = "split_via_immutable",
format = "coco",
arity = 2,
[
open
@index 1
@title "Unsized"
@variant "dynamic_size"
],
[
@index 2
@title "Dynamically Padded"
@variant "dynamic_padding"
]
)]
#[must_use = "has no side effects"]
#[inline(always)]
pub fn via_immutable(self) -> (&'a T, &'a [T::Elem])
where
T: Immutable,
{
let (l, r) = self.into_ptr().via_immutable();
(l.as_ref(), r.as_ref())
}
#[doc = codegen_header!("h5", "split_via_into_bytes")]
#[must_use = "has no side effects"]
#[inline(always)]
pub fn via_into_bytes(self) -> (&'a T, &'a [T::Elem])
where
T: IntoBytes,
{
let (l, r) = self.into_ptr().via_into_bytes();
(l.as_ref(), r.as_ref())
}
#[deprecated(note = "use `Split::via_no_dynamic_padding` instead")]
#[doc(hidden)]
#[must_use = "has no side effects"]
#[inline(always)]
pub fn via_unaligned(self) -> (&'a T, &'a [T::Elem])
where
T: Unaligned,
{
self.via_no_dynamic_padding()
}
#[doc = codegen_header!("h5", "split_via_no_dynamic_padding")]
#[must_use = "has no side effects"]
#[inline(always)]
pub fn via_no_dynamic_padding(self) -> (&'a T, &'a [T::Elem]) {
let (l, r) = self.into_ptr().via_no_dynamic_padding();
(l.as_ref(), r.as_ref())
}
#[doc = codegen_section!(
header = "h5",
bench = "split_via_runtime_check",
format = "coco",
arity = 2,
[
open
@index 1
@title "Unsized"
@variant "dynamic_size"
],
[
@index 2
@title "Dynamically Padded"
@variant "dynamic_padding"
]
)]
#[must_use = "has no side effects"]
#[inline(always)]
pub fn via_runtime_check(self) -> Result<(&'a T, &'a [T::Elem]), Self> {
match self.into_ptr().via_runtime_check() {
Ok((l, r)) => Ok((l.as_ref(), r.as_ref())),
Err(s) => Err(s.into_ref()),
}
}
#[doc = codegen_section!(
header = "h5",
bench = "split_via_unchecked",
format = "coco",
arity = 2,
[
open
@index 1
@title "Unsized"
@variant "dynamic_size"
],
[
@index 2
@title "Dynamically Padded"
@variant "dynamic_padding"
]
)]
#[must_use = "has no side effects"]
#[inline(always)]
pub unsafe fn via_unchecked(self) -> (&'a T, &'a [T::Elem]) {
let (l, r) = unsafe { self.into_ptr().via_unchecked() };
(l.as_ref(), r.as_ref())
}
}
impl<'a, T> Split<&'a mut T>
where
T: ?Sized + SplitAt,
{
#[inline(always)]
fn into_ptr(self) -> Split<Ptr<'a, T, (Exclusive, Aligned, Safe)>> {
let source = Ptr::from_mut(self.source);
unsafe { Split::new(source, self.l_len) }
}
#[must_use = "has no side effects"]
#[inline(always)]
pub fn via_into_bytes(self) -> (&'a mut T, &'a mut [T::Elem])
where
T: IntoBytes,
{
let (l, r) = self.into_ptr().via_into_bytes();
(l.as_mut(), r.as_mut())
}
#[deprecated(note = "use `Split::via_no_dynamic_padding` instead")]
#[doc(hidden)]
#[must_use = "has no side effects"]
#[inline(always)]
pub fn via_unaligned(self) -> (&'a mut T, &'a mut [T::Elem])
where
T: Unaligned,
{
self.via_no_dynamic_padding()
}
#[must_use = "has no side effects"]
#[inline(always)]
pub fn via_no_dynamic_padding(self) -> (&'a mut T, &'a mut [T::Elem]) {
let (l, r) = self.into_ptr().via_no_dynamic_padding();
(l.as_mut(), r.as_mut())
}
#[must_use = "has no side effects"]
#[inline(always)]
pub fn via_runtime_check(self) -> Result<(&'a mut T, &'a mut [T::Elem]), Self> {
match self.into_ptr().via_runtime_check() {
Ok((l, r)) => Ok((l.as_mut(), r.as_mut())),
Err(s) => Err(s.into_mut()),
}
}
#[must_use = "has no side effects"]
#[inline(always)]
pub unsafe fn via_unchecked(self) -> (&'a mut T, &'a mut [T::Elem]) {
let (l, r) = unsafe { self.into_ptr().via_unchecked() };
(l.as_mut(), r.as_mut())
}
}
impl<'a, T, I> Split<Ptr<'a, T, I>>
where
T: ?Sized + SplitAt,
I: Invariants<Alignment = Aligned, Validity = Safe>,
{
fn into_ref(self) -> Split<&'a T>
where
I: Invariants<Aliasing = Shared>,
{
unsafe { Split::new(self.source.as_ref(), self.l_len) }
}
fn into_mut(self) -> Split<&'a mut T>
where
I: Invariants<Aliasing = Exclusive>,
{
unsafe { Split::new(self.source.unify_invariants().as_mut(), self.l_len) }
}
#[inline(always)]
fn l_len(&self) -> MetadataOf<T> {
unsafe { MetadataOf::<T>::new_unchecked(self.l_len) }
}
#[inline(always)]
fn via_immutable(self) -> (Ptr<'a, T, I>, Ptr<'a, [T::Elem], I>)
where
T: Immutable,
I: Invariants<Aliasing = Shared>,
{
unsafe { self.via_unchecked() }
}
#[inline(always)]
fn via_into_bytes(self) -> (Ptr<'a, T, I>, Ptr<'a, [T::Elem], I>)
where
T: IntoBytes,
{
unsafe { self.via_unchecked() }
}
#[inline(always)]
fn via_no_dynamic_padding(self) -> (Ptr<'a, T, I>, Ptr<'a, [T::Elem], I>) {
static_assert!(
T: ?Sized + KnownLayout => !T::LAYOUT.requires_dynamic_padding(),
"`Split::via_no_dynamic_padding` cannot be used with a type whose layout may require dynamic trailing padding; use `Split::via_runtime_check` instead"
);
unsafe { self.via_unchecked() }
}
#[inline(always)]
fn via_runtime_check(self) -> Result<(Ptr<'a, T, I>, Ptr<'a, [T::Elem], I>), Self> {
let l_len = self.l_len();
let trailing_padding = crate::trailing_slice_layout::<T>().padding_for_elems(l_len.get());
if trailing_padding == 0 {
Ok(unsafe { self.via_unchecked() })
} else {
Err(self)
}
}
#[inline(always)]
unsafe fn via_unchecked(self) -> (Ptr<'a, T, I>, Ptr<'a, [T::Elem], I>) {
let l_len = self.l_len();
let inner = self.source.as_inner();
let (left, right) = unsafe { inner.split_at_unchecked(l_len) };
let left = unsafe { Ptr::from_inner(left) };
let right = unsafe { Ptr::from_inner(right) };
(left, right)
}
}
#[cfg(test)]
mod tests {
use core::{cell::Cell, mem::ManuallyDrop};
use crate::{Immutable, KnownLayout, SplitAt, Unalign, Unaligned};
#[derive(KnownLayout, SplitAt, Immutable, Unaligned)]
#[repr(C, packed)]
struct Packed<T: ?Sized> {
prefix: u8,
tail: ManuallyDrop<T>,
}
#[derive(KnownLayout, SplitAt, Immutable)]
#[repr(C, packed(2))]
struct Packed2<T: ?Sized> {
prefix: u8,
tail: ManuallyDrop<T>,
}
#[derive(KnownLayout, SplitAt, Immutable)]
#[repr(C)]
struct Inner<T: ?Sized> {
prefix: u32,
tail: T,
}
#[test]
fn test_split_at_packed() {
use crate::{FromBytes, IntoBytes};
#[derive(FromBytes, IntoBytes, KnownLayout, SplitAt, Immutable, Unaligned)]
#[repr(C, packed)]
struct Packet {
prefix: u8,
tail: [u16],
}
#[repr(align(8))]
struct Bytes([u8; 9]);
for i in 0..=4 {
let mut bytes = Bytes([1; 9]);
let packet = Packet::ref_from_bytes(&bytes.0).unwrap();
let (left, right): (&Packet, &[Unalign<u16>]) =
packet.split_at(i).unwrap().via_immutable();
assert_eq!(core::mem::size_of_val(left), 1 + 2 * i);
assert_eq!(right.len(), 4 - i);
assert!(right.iter().all(|elem| elem.get() == 0x0101));
assert!(left.split_at(i).is_some());
assert!(left.split_at(i + 1).is_none());
assert!(packet.split_at(5).is_none());
let (_, right) = packet.split_at(i).unwrap().via_into_bytes();
assert_eq!(right.len(), 4 - i);
let (_, right) = packet.split_at(i).unwrap().via_no_dynamic_padding();
assert_eq!(right.len(), 4 - i);
let packet = Packet::mut_from_bytes(&mut bytes.0).unwrap();
let (left, right): (&mut Packet, &mut [Unalign<u16>]) =
packet.split_at_mut(i).unwrap().via_runtime_check().ok().unwrap();
left.prefix = 2;
for elem in right {
*elem = Unalign::new(0x0202);
}
assert_eq!(bytes.0[0], 2);
assert!(bytes.0[1..1 + 2 * i].iter().all(|&byte| byte == 1));
assert!(bytes.0[1 + 2 * i..].iter().all(|&byte| byte == 2));
}
}
#[test]
fn test_split_at_nested_packed() {
let mut words = Packed2 { prefix: 0, tail: ManuallyDrop::new([1u32, 2, 3]) };
let dst: &mut Packed2<[u32]> = &mut words;
let (left, right): (&mut _, &mut [Unalign<u32>]) =
dst.split_at_mut(1).unwrap().via_no_dynamic_padding();
left.prefix = 4;
right[0] = Unalign::new(5);
let dst: &Packed2<[u32]> = &words;
let (left, right) = dst.split_at(0).unwrap().via_immutable();
assert_eq!(left.prefix, 4);
assert_eq!(right[0].get(), 1);
assert_eq!(right[1].get(), 5);
assert_eq!(right[2].get(), 3);
let mut packet = Inner {
prefix: 0,
tail: Packed2 {
prefix: 1,
tail: ManuallyDrop::new(Packed {
prefix: 2,
tail: ManuallyDrop::new([3u16, 4, 5]),
}),
},
};
let dst: &Inner<Packed2<Packed<[u16]>>> = &packet;
let (left, right): (&_, &[Unalign<Unalign<u16>>]) =
dst.split_at(1).unwrap().via_immutable();
assert_eq!(left.prefix, 0);
assert_eq!(right.len(), 2);
assert_eq!(right[0].get().get(), 4);
assert_eq!(right[1].get().get(), 5);
let dst: &mut Packed2<Packed<[u16]>> = &mut packet.tail;
let (left, right): (&mut _, &mut [Unalign<Unalign<u16>>]) =
dst.split_at_mut(1).unwrap().via_no_dynamic_padding();
left.prefix = 6;
right[0] = Unalign::new(Unalign::new(7));
let dst: &Inner<Packed2<Packed<[u16]>>> = &packet;
let (_, right) = dst.split_at(0).unwrap().via_immutable();
assert_eq!(right[0].get().get(), 3);
assert_eq!(right[1].get().get(), 7);
assert_eq!(right[2].get().get(), 5);
}
#[test]
fn test_split_at_packed_preserves_inner_padding() {
for i in 0..=4 {
let mut packet = Packed {
prefix: 1,
tail: ManuallyDrop::new(Inner { prefix: 0, tail: [2u8, 3, 4, 5] }),
};
let dst: &Packed<Inner<[u8]>> = &packet;
let (left, right): (&_, &[Unalign<u8>]) = dst.split_at(i).unwrap().via_immutable();
assert_eq!(right.len(), 4 - i);
assert_eq!(right.first().map(Unalign::get), [2, 3, 4, 5].get(i).copied());
let has_padding = core::mem::size_of_val(left) != 5 + i;
assert_eq!(dst.split_at(i).unwrap().via_runtime_check().is_err(), has_padding);
let dst: &mut Packed<Inner<[u8]>> = &mut packet;
let split = dst.split_at_mut(i).unwrap().via_runtime_check();
assert_eq!(split.is_err(), has_padding);
if let Ok((left, right)) = split {
left.prefix = 6;
for elem in right {
*elem = Unalign::new(7);
}
let dst: &Packed<Inner<[u8]>> = &packet;
let (left, right) = dst.split_at(i).unwrap().via_immutable();
assert_eq!(left.prefix, 6);
assert!(right.iter().all(|elem| elem.get() == 7));
}
}
}
#[test]
fn test_split_at_packed_interior_mutation_and_zsts() {
let packet = Packed {
prefix: 0,
tail: ManuallyDrop::new([Cell::new(1u8), Cell::new(2), Cell::new(3)]),
};
let dst: &Packed<[Cell<u8>]> = &packet;
let (left, right): (&_, &[Unalign<Cell<u8>>]) =
dst.split_at(1).unwrap().via_no_dynamic_padding();
left.tail[0].set(4);
right[0].try_deref().unwrap().set(5);
assert_eq!(dst.tail[0].get(), 4);
assert_eq!(dst.tail[1].get(), 5);
assert_eq!(dst.tail[2].get(), 3);
let mut packet = Packed { prefix: 0, tail: ManuallyDrop::new([(); 3]) };
for i in 0..=3 {
let dst: &mut Packed<[()]> = &mut packet;
let (left, right): (&mut _, &mut [Unalign<()>]) =
dst.split_at_mut(i).unwrap().via_no_dynamic_padding();
assert_eq!(left.tail.len(), i);
assert_eq!(right.len(), 3 - i);
assert_eq!(core::mem::size_of_val(left), 1);
}
}
#[cfg(feature = "derive")]
#[test]
fn test_split_at() {
use crate::{FromBytes, Immutable, IntoBytes, KnownLayout, SplitAt};
#[derive(FromBytes, KnownLayout, SplitAt, IntoBytes, Immutable, Debug)]
#[repr(C)]
struct SliceDst<const OFFSET: usize> {
prefix: [u8; OFFSET],
trailing: [u8],
}
#[allow(clippy::as_conversions)]
fn test_split_at<const OFFSET: usize, const BUFFER_SIZE: usize>() {
let n: usize = BUFFER_SIZE - OFFSET;
let arr = [1; BUFFER_SIZE];
let dst = SliceDst::<OFFSET>::ref_from_bytes(&arr[..]).unwrap();
for i in 0..=n {
let (l, r) = dst.split_at(i).unwrap().via_runtime_check().unwrap();
let l_sum: u8 = l.trailing.iter().sum();
let r_sum: u8 = r.iter().sum();
assert_eq!(l_sum, i as u8);
assert_eq!(r_sum, (n - i) as u8);
assert_eq!(l_sum + r_sum, n as u8);
}
let n: usize = BUFFER_SIZE - OFFSET;
let mut arr = [1; BUFFER_SIZE];
let dst = SliceDst::<OFFSET>::mut_from_bytes(&mut arr[..]).unwrap();
for i in 0..=n {
let (l, r) = dst.split_at_mut(i).unwrap().via_runtime_check().unwrap();
let l_sum: u8 = l.trailing.iter().sum();
let r_sum: u8 = r.iter().sum();
assert_eq!(l_sum, i as u8);
assert_eq!(r_sum, (n - i) as u8);
assert_eq!(l_sum + r_sum, n as u8);
}
}
test_split_at::<0, 16>();
test_split_at::<1, 17>();
test_split_at::<2, 18>();
}
#[cfg(feature = "derive")]
#[test]
#[allow(clippy::as_conversions)]
fn test_split_at_overlapping() {
use crate::{FromBytes, Immutable, IntoBytes, KnownLayout, SplitAt};
#[derive(FromBytes, KnownLayout, SplitAt, Immutable)]
#[repr(C, align(2))]
struct SliceDst {
prefix: u8,
trailing: [u8],
}
assert!(SliceDst::LAYOUT.requires_dynamic_padding());
const N: usize = 16;
let arr = [1u16; N];
let dst = SliceDst::ref_from_bytes(arr.as_bytes()).unwrap();
for i in 0..N {
let split = dst.split_at(i).unwrap().via_runtime_check();
if i % 2 == 1 {
assert!(split.is_ok());
} else {
assert!(split.is_err());
}
}
}
#[test]
fn test_split_at_unchecked() {
use crate::SplitAt;
let mut arr = [1, 2, 3, 4];
let slice = &arr[..];
let split = unsafe { SplitAt::split_at_unchecked(slice, 2) };
let (l, r) = unsafe { split.via_unchecked() };
assert_eq!(l, &[1, 2]);
assert_eq!(r, &[3, 4]);
let slice_mut = &mut arr[..];
let split = unsafe { SplitAt::split_at_mut_unchecked(slice_mut, 2) };
let (l, r) = unsafe { split.via_unchecked() };
assert_eq!(l, &mut [1, 2]);
assert_eq!(r, &mut [3, 4]);
}
#[test]
fn test_split_at_via_methods() {
use crate::{FromBytes, Immutable, IntoBytes, KnownLayout, SplitAt};
#[derive(FromBytes, KnownLayout, SplitAt, IntoBytes, Immutable, Debug)]
#[repr(C)]
struct Packet {
length: u8,
body: [u8],
}
let arr = [1, 2, 3, 4];
let packet = Packet::ref_from_bytes(&arr[..]).unwrap();
let split1 = packet.split_at(2).unwrap();
let (l, r) = split1.via_immutable();
assert_eq!(l.length, 1);
assert_eq!(r, &[4]);
let split2 = packet.split_at(2).unwrap();
let (l, r) = split2.via_into_bytes();
assert_eq!(l.length, 1);
assert_eq!(r, &[4]);
}
#[test]
#[allow(deprecated)]
fn test_split_at_via_unaligned() {
use crate::{Immutable, KnownLayout, Split, SplitAt, Unaligned};
fn via_unaligned<'a, T>(split: Split<&'a T>) -> (&'a T, &'a [T::Elem])
where
T: ?Sized + SplitAt + Unaligned,
{
split.via_unaligned()
}
fn via_unaligned_mut<'a, T>(split: Split<&'a mut T>) -> (&'a mut T, &'a mut [T::Elem])
where
T: ?Sized + SplitAt + Unaligned,
{
split.via_unaligned()
}
#[derive(KnownLayout, SplitAt, Immutable, Unaligned)]
#[repr(C)]
struct Packet<B: ?Sized> {
prefix: [u8; 2],
body: B,
}
assert!(!Packet::<[[u8; 2]]>::LAYOUT.requires_dynamic_padding());
let packet = Packet { prefix: [0, 1], body: [[2, 3], [4, 5], [6, 7]] };
let packet: &Packet<[[u8; 2]]> = &packet;
let split = packet.split_at(2).unwrap();
let (l, r) = via_unaligned(split);
assert_eq!(l.body, [[2, 3], [4, 5]]);
assert_eq!(r, &[[6, 7]]);
let mut packet = Packet { prefix: [0, 1], body: [[2, 3], [4, 5], [6, 7]] };
{
let packet: &mut Packet<[[u8; 2]]> = &mut packet;
let split = packet.split_at_mut(2).unwrap();
let (l, r) = via_unaligned_mut(split);
l.body[0] = [8, 9];
r[0] = [10, 11];
}
assert_eq!(packet.body, [[8, 9], [4, 5], [10, 11]]);
}
#[test]
fn test_split_at_via_no_dynamic_padding() {
use core::cell::Cell;
use crate::SplitAt;
let words = [1u16, 2, 3, 4];
let split = SplitAt::split_at(&words[..], 2).unwrap();
let (left, right) = split.via_no_dynamic_padding();
assert_eq!(left, [1, 2]);
assert_eq!(right, [3, 4]);
let mut words = [1u16, 2, 3, 4];
let split = SplitAt::split_at_mut(&mut words[..], 2).unwrap();
let (left, right) = split.via_no_dynamic_padding();
left[0] = 5;
right[0] = 6;
assert_eq!(words, [5, 2, 6, 4]);
let cells = [Cell::new(1u16), Cell::new(2), Cell::new(3)];
let split = SplitAt::split_at(&cells[..], 2).unwrap();
let (left, right) = split.via_no_dynamic_padding();
left[0].set(4);
right[0].set(5);
assert_eq!([cells[0].get(), cells[1].get(), cells[2].get()], [4, 2, 5]);
}
}