From 046f3cc4c1936130df6c4730e6de61e0c5099a84 Mon Sep 17 00:00:00 2001 From: Tage Johansson Date: Wed, 17 Dec 2025 14:08:52 +0100 Subject: [PATCH 1/9] Remove the `single` module and rename vec.rs to lib.rs. (Breaking Change) --- src/lib.rs | 1071 ++++++++++++++++++++++++++++++++--- src/single.rs | 465 --------------- src/vec.rs | 981 -------------------------------- tests/std_required_tests.rs | 43 -- 4 files changed, 981 insertions(+), 1579 deletions(-) delete mode 100644 src/single.rs delete mode 100644 src/vec.rs diff --git a/src/lib.rs b/src/lib.rs index a659562..c90af9f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,90 +1,981 @@ -//! `FixedSliceVec` is a dynamic length Vec with runtime-determined maximum capacity backed by a slice. -//! -//! ## Overview -//! -//! This library is focused on meeting the following, narrow use case: -//! * **`no_std`** : Rust programming without the std library. -//! * **No global allocator**: No access to the `alloc` crate -//! * **Runtime capacity** : Maximum possible items in a collection or maximum -//! possible backing bytes of storage is unknown until runtime. -//! -//! ## Getting Started -//! -//! `fixed-slice-vec` is a Rust library, built and tested via Cargo. It -//! has no dependencies outside of the Rust core library. -//! -//! To add `fixed-slice-vec` to your Rust project, add a dependency to it -//! in your Cargo.toml file. -//! -//! ```toml -//! fixed-slice-vec = "0.10.0" -//! ``` -//! -//! ## Usage -//! -//! ### FixedSliceVec -//! -//! In your Rust project source code, you can create a FixedSliceVec a number of -//! ways (see the project Rust API docs for details). -//! The most common form of construction is from a slice of uninitialized bytes. -//! -//! ```rust -//! use fixed_slice_vec::FixedSliceVec; -//! use core::mem::MaybeUninit; -//! // Safe to construct arrays of uninitialized values. -//! let mut bytes: [MaybeUninit; 1024] = unsafe { MaybeUninit::uninit().assume_init() }; -//! let byte_slice = &mut bytes[..512]; -//! let mut vec: FixedSliceVec = FixedSliceVec::from_uninit_bytes(byte_slice); -//! -//! assert_eq!(0, vec.len()); -//! assert!(vec.capacity() >= 63, "The exact capacity will depend on source-slice alignment"); -//! -//! vec.try_push(2.7f64).expect("Ran out of capacity unexpectedly"); -//! assert_eq!(1, vec.len()); -//! -//! vec.clear(); -//! assert!(vec.is_empty()); -//! ``` -//! -//! ### single module -//! -//! As a companion to `FixedSliceVec`, the `single` submodule provides -//! functions for working with individual Rust values backed by arbitrary -//! byte slices. See the API Docs for details and examples. -//! -//! ### Comparison -//! -//! Several other `Vec`-like crates exist and should be considered -//! as possible alternatives to `FixedSliceVec`. -//! -//! * The standard library's [Vec](https://doc.rust-lang.org/std/vec/struct.Vec.html) -//! has a runtime dynamic capacity backed by an allocator. This should probably -//! be your first choice if you have access to an allocator. -//! * [ArrayVec](https://crates.io/crates/arrayvec) has a compile-time -//! fixed capacity. It is widely used and available on stable. -//! * [StaticVec](https://crates.io/crates/staticvec) has a compile-time -//! fixed capacity. It uses recent const generic features and is currently -//! nightly-only. -//! * [SliceVec](https://crates.io/crates/slicevec) has runtime fixed capacity. -//! This is the closest in its target use case to `FixedSliceVec`. We -//! only discovered it existed after developing `FixedSliceVec`, so there's some -//! evidence of convergent design or needs. It appears largely -//! unmaintained over the last few years, does not make use of the -//! [MaybeUninit](https://doc.rust-lang.org/std/mem/union.MaybeUninit.html) -//! pattern for handling uninitialized data in Rust, and does not drop items -//! correctly in some cases. It does not support creating an instance from raw bytes -//! and requires `Default` elements for some operations. -//! -//! -//! ## License -//! -//! Copyright 2020 Auxon Corporation, released under the [Apache 2.0 license](./LICENSE). -#![no_std] -#![deny(warnings)] -#![deny(missing_docs)] -#![deny(clippy::all)] - -pub mod single; -pub mod vec; - -pub use crate::vec::*; +//! FixedSliceVec is a structure for defining variably populated vectors backed +//! by a slice of storage capacity. +use core::borrow::{Borrow, BorrowMut}; +use core::convert::From; +use core::hash::{Hash, Hasher}; +use core::iter::Extend; +use core::mem::MaybeUninit; +use core::ops::{Deref, DerefMut}; + +/// Vec-like structure backed by a storage slice of possibly uninitialized data. +/// +/// The maximum length (the capacity) is fixed at runtime to the length of the +/// provided storage slice. +pub struct FixedSliceVec<'a, T: Sized> { + /// Backing storage, provides capacity + storage: &'a mut [MaybeUninit], + /// The number of items that have been + /// initialized + len: usize, +} + +impl<'a, T: Sized> Drop for FixedSliceVec<'a, T> { + fn drop(&mut self) { + self.clear(); + } +} + +impl<'a, T: Sized> FixedSliceVec<'a, T> { + /// Create a FixedSliceVec backed by a slice of possibly-uninitialized data. + /// The backing storage slice is used as capacity for Vec-like operations, + /// + /// The initial length of the FixedSliceVec is 0. + #[inline] + pub fn new(storage: &'a mut [MaybeUninit]) -> Self { + FixedSliceVec { storage, len: 0 } + } + + /// Create a well-aligned FixedSliceVec backed by a slice of the provided bytes. + /// The slice is as large as possible given the item type and alignment of + /// the provided bytes. + /// + /// If you are interested in recapturing the prefix and suffix bytes on + /// either side of the carved-out FixedSliceVec buffer, consider using `align_from_bytes` instead: + /// + /// ``` + /// # let mut bytes = [3u8, 1, 4, 1, 5, 9]; + /// let vec = unsafe { fixed_slice_vec::FixedSliceVec::from_bytes(&mut bytes[..]) }; + /// # let vec: fixed_slice_vec::FixedSliceVec = vec; + /// ``` + /// + /// The bytes are treated as if they might be uninitialized, so even if `T` is `u8`, + /// the length of the returned `FixedSliceVec` will be zero. + /// + /// # Safety + /// + /// If the item type `T` of the FixedSliceVec contains any padding bytes + /// then those padding bytes may be observable in the provided slice + /// after the `FixedSliceVec` is dropped. Observing padding bytes is + /// undefined behavior. + #[inline] + pub unsafe fn from_bytes(bytes: &'a mut [u8]) -> FixedSliceVec<'a, T> { + FixedSliceVec::align_from_bytes(bytes).1 + } + + /// Create a well-aligned FixedSliceVec backed by a slice of the provided + /// uninitialized bytes. The typed slice is as large as possible given its + /// item type and the alignment of the provided bytes. + /// + /// If you are interested in recapturing the prefix and suffix bytes on + /// either side of the carved-out FixedSliceVec buffer, consider using `align_from_uninit_bytes`: + /// + #[inline] + pub fn from_uninit_bytes(bytes: &'a mut [MaybeUninit]) -> FixedSliceVec<'a, T> { + FixedSliceVec::align_from_uninit_bytes(bytes).1 + } + + /// Create a well-aligned FixedSliceVec backed by a slice of the provided bytes. + /// The slice is as large as possible given the item type and alignment of + /// the provided bytes. Returns the unused prefix and suffix bytes on + /// either side of the carved-out FixedSliceVec. + /// + /// ``` + /// let mut bytes = [3u8, 1, 4, 1, 5, 9]; + /// let (prefix, vec, suffix) = unsafe { fixed_slice_vec::FixedSliceVec::align_from_bytes(&mut bytes[..]) }; + /// let vec: fixed_slice_vec::FixedSliceVec = vec; + /// ``` + /// + /// The bytes are treated as if they might be uninitialized, so even if `T` is `u8`, + /// the length of the returned `FixedSliceVec` will be zero. + /// + /// # Safety + /// + /// If the item type `T` of the FixedSliceVec contains any padding bytes + /// then those padding bytes may be observable in the provided slice + /// after the `FixedSliceVec` is dropped. Observing padding bytes is + /// undefined behavior. + #[inline] + pub unsafe fn align_from_bytes( + bytes: &'a mut [u8], + ) -> (&'a mut [u8], FixedSliceVec<'a, T>, &'a mut [u8]) { + let (prefix, storage, suffix) = bytes.align_to_mut(); + (prefix, FixedSliceVec { storage, len: 0 }, suffix) + } + + /// Create a well-aligned FixedSliceVec backed by a slice of the provided bytes. + /// The slice is as large as possible given the item type and alignment of + /// the provided bytes. Returns the unused prefix and suffix bytes on + /// either side of the carved-out FixedSliceVec. + /// + /// ``` + /// # use core::mem::MaybeUninit; + /// let mut bytes: [MaybeUninit; 15] = unsafe { MaybeUninit::uninit().assume_init() }; + /// let (prefix, vec, suffix) = fixed_slice_vec::FixedSliceVec::align_from_uninit_bytes(&mut + /// bytes[..]); + /// let vec: fixed_slice_vec::FixedSliceVec = vec; + /// ``` + /// + /// The length of the returned `FixedSliceVec` will be zero. + #[inline] + pub fn align_from_uninit_bytes( + bytes: &'a mut [MaybeUninit], + ) -> ( + &'a mut [MaybeUninit], + FixedSliceVec<'a, T>, + &'a mut [MaybeUninit], + ) { + let (prefix, storage, suffix) = unsafe { bytes.align_to_mut() }; + (prefix, FixedSliceVec { storage, len: 0 }, suffix) + } + + /// Returns an unsafe mutable pointer to the FixedSliceVec's buffer. + /// + /// The caller must ensure that the FixedSliceVec and the backing + /// storage data for the FixedSliceVec (provided at construction) + /// outlives the pointer this function returns. + /// + /// Furthermore, the contents of the buffer are not guaranteed + /// to have been initialized at indices >= len. + /// + /// # Examples + /// + /// ``` + /// use core::mem::MaybeUninit; + /// let mut storage: [MaybeUninit; 16] = unsafe { MaybeUninit::uninit().assume_init() }; + /// let mut x: fixed_slice_vec::FixedSliceVec = fixed_slice_vec::FixedSliceVec::from_uninit_bytes(&mut storage[..]); + /// assert!(x.try_extend([1u16, 2, 4, 8].iter().copied()).is_ok()); + /// let size = x.len(); + /// let x_ptr = x.as_mut_ptr(); + /// + /// // Set elements via raw pointer writes. + /// unsafe { + /// for i in 0..size { + /// *x_ptr.add(i) = MaybeUninit::new(i as u16); + /// } + /// } + /// assert_eq!(&*x, &[0,1,2,3]); + /// ``` + #[inline] + pub fn as_mut_ptr(&mut self) -> *mut MaybeUninit { + self.storage.as_mut_ptr() + } + /// Returns a raw pointer to the FixedSliceVec's buffer. + /// + /// The caller must ensure that the FixedSliceVec and the backing + /// storage data for the FixedSliceVec (provided at construction) + /// outlives the pointer this function returns. + /// + /// Furthermore, the contents of the buffer are not guaranteed + /// to have been initialized at indices >= len. + /// + /// The caller must also ensure that the memory the pointer (non-transitively) points to + /// is never written to using this pointer or any pointer derived from it. + /// + /// If you need to mutate the contents of the slice with pointers, use [`as_mut_ptr`]. + /// + /// # Examples + /// + /// ``` + /// use core::mem::MaybeUninit; + /// let mut storage: [MaybeUninit; 16] = unsafe { MaybeUninit::uninit().assume_init() }; + /// let mut x: fixed_slice_vec::FixedSliceVec = fixed_slice_vec::FixedSliceVec::from_uninit_bytes(&mut storage[..]); + /// x.extend([1u16, 2, 4].iter().copied()); + /// let x_ptr = x.as_ptr(); + /// + /// unsafe { + /// for i in 0..x.len() { + /// assert_eq!((*x_ptr.add(i)).assume_init(), 1 << i); + /// } + /// } + /// ``` + /// + /// [`as_mut_ptr`]: #method.as_mut_ptr + #[inline] + pub fn as_ptr(&self) -> *const MaybeUninit { + self.storage.as_ptr() + } + + /// The length of the FixedSliceVec. The number of initialized + /// values that have been added to it. + #[inline] + pub fn len(&self) -> usize { + self.len + } + + /// The maximum amount of items that can live in this FixedSliceVec + #[inline] + pub fn capacity(&self) -> usize { + self.storage.len() + } + + /// Returns true if there are no items present. + #[inline] + pub fn is_empty(&self) -> bool { + self.len == 0 + } + + /// Returns true if the FixedSliceVec is full to capacity. + #[inline] + pub fn is_full(&self) -> bool { + self.len == self.capacity() + } + + /// Attempt to add a value to the FixedSliceVec. + /// + /// Returns an error if there is not enough capacity to hold another item. + #[inline] + pub fn try_push(&mut self, value: T) -> Result<(), StorageError> { + if self.is_full() { + return Err(StorageError(value)); + } + self.storage[self.len] = MaybeUninit::new(value); + self.len += 1; + Ok(()) + } + + /// Attempt to add a value to the FixedSliceVec. + /// + /// # Panics + /// + /// Panics if there is not sufficient capacity to hold another item. + #[inline] + pub fn push(&mut self, value: T) { + self.try_push(value).unwrap(); + } + + /// Attempt to insert a value to the FixedSliceVec. + /// + /// Returns an error if there is not enough capacity to hold another item. + #[inline] + pub fn try_insert(&mut self, index: usize, value: T) -> Result<(), StorageError> { + if index > self.len() { + return Err(StorageError(value)); + } + self.try_push(value)?; + self.as_mut_slice()[index..].rotate_right(1); + Ok(()) + } + + /// Inserts a value to the FixedSliceVec. + /// + /// # Panics + /// + /// Panics if there is not sufficient capacity to hold another item. + #[inline] + pub fn insert(&mut self, index: usize, value: T) { + self.try_insert(index, value).unwrap() + } + + /// Attempt to add as many values as will fit from an iterable. + /// + /// Returns Ok(()) if all of the items in the iterator can fit into `self`. + /// Returns an Err containing the iterator if `self` fills up and there + /// are items remaining in the iterator. + #[inline] + pub fn try_extend( + &mut self, + iterable: impl IntoIterator, + ) -> Result<(), impl Iterator> { + let mut iter = iterable.into_iter().peekable(); + loop { + if iter.peek().is_some() { + if self.is_full() { + return Err(iter); + } else if let Some(item) = iter.next() { + self.storage[self.len] = MaybeUninit::new(item); + self.len += 1; + } else { + unreachable!("`FixedSliceVec::try_extend` peeked above to ensure that `next` would return Some") + } + } else { + return Ok(()); + } + } + } + + /// Remove the last item from the FixedSliceVec. + #[inline] + pub fn pop(&mut self) -> Option { + if self.len == 0 { + return None; + } + self.len -= 1; + Some(unsafe { self.storage[self.len].as_ptr().read() }) + } + + /// Removes the FixedSliceVec's tracking of all items in it while retaining the + /// same capacity. + #[inline] + pub fn clear(&mut self) { + let original_len = self.len; + self.len = 0; + unsafe { + // Note we cannot use the usual DerefMut helper to produce a slice because it relies + // on the `len` field, which we have updated above already. + // The early setting of `len` is designed to avoid double-free errors if there + // is a panic in the middle of the following drop. + (core::slice::from_raw_parts_mut(self.storage.as_mut_ptr() as *mut T, original_len) + as *mut [T]) + .drop_in_place(); + } + } + + /// Shortens the FixedSliceVec, keeping the first `len` elements and dropping the rest. + /// + /// If len is greater than the current length, this has no effect. + /// Note that this method has no effect on the capacity of the FixedSliceVec. + #[inline] + pub fn truncate(&mut self, len: usize) { + let original_len = self.len; + if len > original_len { + return; + } + self.len = len; + unsafe { + // Note we cannot use the usual DerefMut helper to produce a slice because it relies + // on the `len` field, which we have updated above already. + // The early setting of `len` is designed to avoid double-free errors if there + // is a panic in the middle of the following drop. + (&mut core::slice::from_raw_parts_mut(self.storage.as_mut_ptr() as *mut T, original_len)[len..] as *mut [T]).drop_in_place(); + } + } + /// Removes and returns the element at position `index` within the FixedSliceVec, + /// shifting all elements after it to the left. + /// + /// # Panics + /// + /// Panics if `index` is out of bounds. + pub fn remove(&mut self, index: usize) -> T { + // Error message and overall impl strategy following along with std vec, + if index >= self.len { + panic!( + "removal index (is {}) should be < len (is {})", + index, self.len + ); + } + unsafe { self.unchecked_remove(index) } + } + + /// Removes and returns the element at position `index` within the FixedSliceVec, + /// shifting all elements after it to the left. + pub fn try_remove(&mut self, index: usize) -> Result { + if index >= self.len { + return Err(IndexError); + } + Ok(unsafe { self.unchecked_remove(index) }) + } + + /// Remove and return an element without checking if it's actually there. + #[inline] + unsafe fn unchecked_remove(&mut self, index: usize) -> T { + let ptr = (self.as_mut_ptr() as *mut T).add(index); + let out = core::ptr::read(ptr); + core::ptr::copy(ptr.offset(1), ptr, self.len - index - 1); + self.len -= 1; + out + } + /// Removes an element from the vector and returns it. + /// + /// The removed element is replaced by the last element of the vector. + /// + /// This does not preserve ordering, but is O(1). + /// + /// # Panics + /// + /// Panics if `index` is out of bounds. + pub fn swap_remove(&mut self, index: usize) -> T { + if index >= self.len { + panic!( + "swap_remove index (is {}) should be < len (is {})", + index, self.len + ); + } + unsafe { self.unchecked_swap_remove(index) } + } + /// Removes an element from the vector and returns it. + /// + /// The removed element is replaced by the last element of the vector. + /// + /// This does not preserve ordering, but is O(1). + pub fn try_swap_remove(&mut self, index: usize) -> Result { + if index >= self.len { + return Err(IndexError); + } + Ok(unsafe { self.unchecked_swap_remove(index) }) + } + + /// swap_remove, without the length-checking + #[inline] + unsafe fn unchecked_swap_remove(&mut self, index: usize) -> T { + let target_ptr = (self.as_mut_ptr() as *mut T).add(index); + let end_ptr = (self.as_ptr() as *const T).add(self.len - 1); + let end_value = core::ptr::read(end_ptr); + self.len -= 1; + core::ptr::replace(target_ptr, end_value) + } + + /// Obtain an immutable slice view on the initialized portion of the + /// FixedSliceVec. + #[inline] + pub fn as_slice(&self) -> &[T] { + Deref::deref(self) + } + + /// Obtain a mutable slice view on the initialized portion of the + /// FixedSliceVec. + #[inline] + pub fn as_mut_slice(&mut self) -> &mut [T] { + DerefMut::deref_mut(self) + } +} + +/// Error that occurs when a call that attempts to increase +/// the number of items in the FixedSliceVec fails +/// due to insufficient storage capacity. +#[derive(Clone, PartialEq, PartialOrd, Eq, Ord)] +pub struct StorageError(pub T); + +impl core::fmt::Debug for StorageError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { + f.write_str("Push failed because FixedSliceVec was full") + } +} + +/// Error that occurs when a call that attempts to access +/// the FixedSliceVec in a manner that does not respect +/// the current length of the vector, i.e. its current +/// number of initialized items. +#[derive(Clone, PartialEq, PartialOrd, Eq, Ord)] +pub struct IndexError; + +impl core::fmt::Debug for IndexError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { + f.write_str( + "Access to the FixedSliceVec failed because an invalid index or length was provided", + ) + } +} + +impl<'a, T: Sized> From<&'a mut [MaybeUninit]> for FixedSliceVec<'a, T> { + #[inline] + fn from(v: &'a mut [MaybeUninit]) -> Self { + FixedSliceVec { storage: v, len: 0 } + } +} + +impl<'a, T: Sized> Hash for FixedSliceVec<'a, T> +where + T: Hash, +{ + #[inline] + fn hash(&self, state: &mut H) { + Hash::hash(&**self, state) + } +} + +impl<'a, T: Sized> PartialEq for FixedSliceVec<'a, T> +where + T: PartialEq, +{ + #[inline] + fn eq(&self, other: &Self) -> bool { + **self == **other + } +} + +impl<'a, T: Sized> PartialEq<[T]> for FixedSliceVec<'a, T> +where + T: PartialEq, +{ + #[inline] + fn eq(&self, other: &[T]) -> bool { + **self == *other + } +} + +impl<'a, T: Sized> Eq for FixedSliceVec<'a, T> where T: Eq {} + +impl<'a, T: Sized> Borrow<[T]> for FixedSliceVec<'a, T> { + #[inline] + fn borrow(&self) -> &[T] { + self + } +} + +impl<'a, T: Sized> BorrowMut<[T]> for FixedSliceVec<'a, T> { + #[inline] + fn borrow_mut(&mut self) -> &mut [T] { + self + } +} + +impl<'a, T: Sized> AsRef<[T]> for FixedSliceVec<'a, T> { + #[inline] + fn as_ref(&self) -> &[T] { + self + } +} + +impl<'a, T: Sized> AsMut<[T]> for FixedSliceVec<'a, T> { + #[inline] + fn as_mut(&mut self) -> &mut [T] { + self + } +} + +impl<'a, T: Sized> core::fmt::Debug for FixedSliceVec<'a, T> +where + T: core::fmt::Debug, +{ + #[inline] + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + (**self).fmt(f) + } +} + +impl<'a, T: Sized> PartialOrd for FixedSliceVec<'a, T> +where + T: PartialOrd, +{ + #[inline] + fn partial_cmp(&self, other: &FixedSliceVec<'a, T>) -> Option { + (**self).partial_cmp(other) + } + + #[inline] + fn lt(&self, other: &Self) -> bool { + (**self).lt(other) + } + + #[inline] + fn le(&self, other: &Self) -> bool { + (**self).le(other) + } + + #[inline] + fn gt(&self, other: &Self) -> bool { + (**self).gt(other) + } + + #[inline] + fn ge(&self, other: &Self) -> bool { + (**self).ge(other) + } +} + +impl<'a, T: Sized> Deref for FixedSliceVec<'a, T> { + type Target = [T]; + #[inline] + fn deref(&self) -> &Self::Target { + unsafe { core::slice::from_raw_parts(self.storage.as_ptr() as *const T, self.len) } + } +} + +impl<'a, T: Sized> DerefMut for FixedSliceVec<'a, T> { + #[inline] + fn deref_mut(&mut self) -> &mut [T] { + unsafe { core::slice::from_raw_parts_mut(self.storage.as_mut_ptr() as *mut T, self.len) } + } +} + +/// Adds as many items from the provided iterable as can fit. +/// +/// Gives no indication of how many were extracted or if some +/// could not fit. +/// +/// Use `FixedSliceVec::try_extend` if you require more fine- +/// grained signal about the outcome of attempted extension. +impl<'a, T: Sized> Extend for FixedSliceVec<'a, T> { + fn extend>(&mut self, iter: I) { + let _ = self.try_extend(iter); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn from_uninit() { + let mut data: [MaybeUninit; 32] = unsafe { MaybeUninit::uninit().assume_init() }; + let mut sv: FixedSliceVec = (&mut data[..]).into(); + assert_eq!(0, sv.len()); + assert_eq!(32, sv.capacity()); + assert!(sv.is_empty()); + let sv_as_slice: &[u8] = &sv; + let empty_slice: &[u8] = &[]; + assert_eq!(empty_slice, sv_as_slice); + assert_eq!(Ok(()), sv.try_push(3)); + assert_eq!(Ok(()), sv.try_push(1)); + assert_eq!(Ok(()), sv.try_push(4)); + let non_empty_slice: &[u8] = &[3u8, 1, 4]; + assert_eq!(non_empty_slice, &sv as &[u8]); + let sv_as_mut_slice: &mut [u8] = &mut sv; + sv_as_mut_slice[1] = 2; + let non_empty_slice: &[u8] = &[3u8, 2, 4]; + assert_eq!(non_empty_slice, &sv as &[u8]); + + sv.clear(); + assert_eq!(0, sv.len()); + assert!(sv.is_empty()); + } + + #[test] + fn happy_path_from_bytes() { + let mut data = [0u8; 31]; + let mut sv: FixedSliceVec = unsafe { FixedSliceVec::from_bytes(&mut data[..]) }; + assert!(sv.is_empty()); + // capacity might be 0 if miri messes with the align-ability of pointers + if sv.capacity() > 0 { + for i in 0..sv.capacity() { + assert_eq!(Ok(()), sv.try_push(i)); + } + } + assert!(sv.is_full()); + } + + #[test] + fn align_captures_suffix_and_prefix() { + let mut data = [ + 3u8, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9, 3, 2, 3, 8, 4, 6, 2, 6, 4, 3, 3, + ]; + let original_len = data.len(); + for i in 0..original_len { + for len in 0..original_len - i { + let storage = &mut data[i..i + len]; + let storage_len = storage.len(); + let (prefix, fixed_slice_vec, suffix): (_, FixedSliceVec, _) = + unsafe { FixedSliceVec::align_from_bytes(storage) }; + assert_eq!( + storage_len, + prefix.len() + 2 * fixed_slice_vec.capacity() + suffix.len() + ); + } + } + } + fn uninit_storage() -> [MaybeUninit; 4] { + unsafe { MaybeUninit::uninit().assume_init() } + } + + #[test] + fn as_ptr_reveals_expected_internal_content() { + let expected = [0u8, 1, 2, 3]; + let mut storage = uninit_storage(); + let mut fsv = FixedSliceVec::from_uninit_bytes(&mut storage[..]); + assert!(fsv.try_extend(expected.iter().copied()).is_ok()); + + let ptr = fsv.as_ptr(); + for i in 0..fsv.len() { + assert_eq!(expected[i], unsafe { (*ptr.add(i)).assume_init() }); + } + + let mut fsv = fsv; + fsv[3] = 99; + assert_eq!(99, unsafe { (*fsv.as_ptr().add(3)).assume_init() }) + } + + #[test] + fn as_mut_ptr_allows_changes_to_internal_content() { + let expected = [0u8, 2, 4, 8]; + let mut storage = uninit_storage(); + let mut fsv: FixedSliceVec = FixedSliceVec::from_uninit_bytes(&mut storage[..]); + assert!(fsv.try_extend(expected.iter().copied()).is_ok()); + + assert_eq!(8, unsafe { fsv.as_mut_ptr().add(3).read().assume_init() }); + unsafe { + fsv.as_mut_ptr().add(3).write(MaybeUninit::new(99)); + } + assert_eq!(99, fsv[3]); + + fsv[1] = 200; + assert_eq!(200, unsafe { fsv.as_mut_ptr().add(1).read().assume_init() }); + } + + #[test] + fn manual_truncate() { + let expected = [0u8, 2, 4, 8]; + let mut storage = uninit_storage(); + let mut fsv = FixedSliceVec::from_uninit_bytes(&mut storage[..]); + assert!(fsv.try_extend(expected.iter().copied()).is_ok()); + + fsv.truncate(100); + assert_eq!(&[0u8, 2, 4, 8], fsv.as_slice()); + fsv.truncate(2); + assert_eq!(&[0u8, 2], fsv.as_slice()); + fsv.truncate(2); + assert_eq!(&[0u8, 2], fsv.as_slice()); + fsv.truncate(0); + assert!(fsv.is_empty()); + } + + #[test] + fn manual_try_remove() { + let expected = [0u8, 2, 4, 8]; + let mut storage = uninit_storage(); + let mut fsv = FixedSliceVec::from_uninit_bytes(&mut storage[..]); + assert!(fsv.try_extend(expected.iter().copied()).is_ok()); + + assert_eq!(Err(IndexError), fsv.try_remove(100)); + assert_eq!(Err(IndexError), fsv.try_remove(4)); + assert_eq!(&[0u8, 2, 4, 8], fsv.as_slice()); + assert_eq!(Ok(2), fsv.try_remove(1)); + assert_eq!(&[0u8, 4, 8], fsv.as_slice()); + } + + #[test] + #[should_panic] + fn manual_swap_remove_outside_range() { + let mut storage = uninit_storage(); + let mut fsv = FixedSliceVec::from_uninit_bytes(&mut storage[..]); + assert!(fsv.try_extend([0u8, 2, 4, 8].iter().copied()).is_ok()); + fsv.swap_remove(100); + } + + #[test] + #[should_panic] + fn manual_swap_remove_empty() { + let mut storage = uninit_storage(); + let mut fsv: FixedSliceVec = FixedSliceVec::from_uninit_bytes(&mut storage[..]); + assert!(fsv.capacity() > 0); + assert_eq!(0, fsv.len()); + fsv.swap_remove(0); + } + + #[test] + fn manual_swap_remove_inside_range() { + let mut storage = uninit_storage(); + let mut fsv = FixedSliceVec::from_uninit_bytes(&mut storage[..]); + assert!(fsv.try_extend([0u8, 2, 4, 8].iter().copied()).is_ok()); + assert_eq!(&[0u8, 2, 4, 8], fsv.as_slice()); + assert_eq!(2, fsv.swap_remove(1)); + assert_eq!(&[0u8, 8, 4], fsv.as_slice()); + assert_eq!(0, fsv.swap_remove(0)); + assert_eq!(&[4u8, 8], fsv.as_slice()); + assert_eq!(4, fsv.swap_remove(0)); + assert_eq!(&[8u8], fsv.as_slice()); + assert_eq!(8, fsv.swap_remove(0)); + assert!(fsv.as_slice().is_empty()); + } + + #[test] + fn manual_try_swap_remove() { + let expected = [0u8, 2, 4, 8]; + let mut storage = uninit_storage(); + let mut fsv = FixedSliceVec::from_uninit_bytes(&mut storage[..]); + assert!(fsv.try_extend(expected.iter().copied()).is_ok()); + + assert_eq!(Err(IndexError), fsv.try_swap_remove(100)); + assert_eq!(Err(IndexError), fsv.try_swap_remove(4)); + assert_eq!(&[0u8, 2, 4, 8], fsv.as_slice()); + assert_eq!(Ok(2), fsv.try_swap_remove(1)); + assert_eq!(&[0u8, 8, 4], fsv.as_slice()); + assert_eq!(Ok(0), fsv.try_swap_remove(0)); + assert_eq!(&[4u8, 8], fsv.as_slice()); + assert_eq!(Ok(4), fsv.try_swap_remove(0)); + assert_eq!(&[8u8], fsv.as_slice()); + assert_eq!(Ok(8), fsv.try_swap_remove(0)); + assert!(fsv.as_slice().is_empty()); + assert_eq!(Err(IndexError), fsv.try_swap_remove(0)); + } + + #[test] + fn try_extend_with_exactly_enough_room() { + let expected = [0u8, 2, 4, 8]; + let mut storage = uninit_storage(); + let mut fsv = FixedSliceVec::from_uninit_bytes(&mut storage[..]); + assert!(fsv.try_extend(expected.iter().copied()).is_ok()); + assert_eq!(&expected[..], &fsv[..]); + } + + #[test] + fn try_extend_with_more_than_enough_room() { + let expected = [0u8, 2, 4, 8]; + let mut storage: [MaybeUninit; 100] = unsafe { MaybeUninit::uninit().assume_init() }; + let mut fsv = FixedSliceVec::from_uninit_bytes(&mut storage[..]); + assert!(fsv.try_extend(expected.iter().copied()).is_ok()); + assert_eq!(&expected[..], &fsv[..]); + } + + #[test] + fn try_extend_with_not_enough_room() { + let expected = [0u8, 2, 4, 8]; + let mut storage: [MaybeUninit; 2] = unsafe { MaybeUninit::uninit().assume_init() }; + let mut fsv = FixedSliceVec::from_uninit_bytes(&mut storage[..]); + let mut out_iter = fsv.try_extend(expected.iter().copied()).unwrap_err(); + assert_eq!(Some(4), out_iter.next()); + assert_eq!(Some(8), out_iter.next()); + assert_eq!(None, out_iter.next()); + assert_eq!(&expected[0..2], &fsv[..]); + } + #[test] + fn extend_with_exactly_enough_room() { + let expected = [0u8, 2, 4, 8]; + let mut storage = uninit_storage(); + let mut fsv = FixedSliceVec::from_uninit_bytes(&mut storage[..]); + fsv.extend(expected.iter().copied()); + assert_eq!(&expected[..], &fsv[..]); + } + + #[test] + fn extend_with_more_than_enough_room() { + let expected = [0u8, 2, 4, 8]; + let mut storage: [MaybeUninit; 100] = unsafe { MaybeUninit::uninit().assume_init() }; + let mut fsv = FixedSliceVec::from_uninit_bytes(&mut storage[..]); + fsv.extend(expected.iter().copied()); + assert_eq!(&expected[..], &fsv[..]); + } + + #[test] + fn extend_with_not_enough_room() { + let expected = [0u8, 2, 4, 8]; + let mut storage: [MaybeUninit; 2] = unsafe { MaybeUninit::uninit().assume_init() }; + let mut fsv = FixedSliceVec::from_uninit_bytes(&mut storage[..]); + fsv.extend(expected.iter().copied()); + assert_eq!(&expected[0..2], &fsv[..]); + } + + #[test] + fn from_uninit_bytes_empty_slice() { + let storage: &mut [MaybeUninit] = &mut []; + let mut fsv: FixedSliceVec = FixedSliceVec::from_uninit_bytes(storage); + assert_eq!(0, fsv.capacity()); + assert_eq!(0, fsv.len()); + assert!(fsv.try_push(31).is_err()); + } + #[test] + fn from_uninit_bytes_smaller_than_item_slice() { + let mut storage: [MaybeUninit; 1] = unsafe { MaybeUninit::uninit().assume_init() }; + let mut fsv: FixedSliceVec = FixedSliceVec::from_uninit_bytes(&mut storage); + assert_eq!(0, fsv.capacity()); + assert_eq!(0, fsv.len()); + assert!(fsv.try_push(31).is_err()); + } + #[test] + fn from_uninit_bytes_larger_than_item_slice() { + let mut storage: [MaybeUninit; 9] = unsafe { MaybeUninit::uninit().assume_init() }; + let mut fsv: FixedSliceVec = FixedSliceVec::from_uninit_bytes(&mut storage); + assert!(fsv.capacity() > 0); + assert_eq!(0, fsv.len()); + assert!(fsv.try_push(31).is_ok()); + assert_eq!(&[31], &fsv[..]); + } + + #[test] + fn equality_sanity_checks() { + let mut storage_a: [MaybeUninit; 2] = unsafe { MaybeUninit::uninit().assume_init() }; + let mut a: FixedSliceVec = FixedSliceVec::from_uninit_bytes(&mut storage_a); + let mut storage_b: [MaybeUninit; 2] = unsafe { MaybeUninit::uninit().assume_init() }; + let mut b: FixedSliceVec = FixedSliceVec::from_uninit_bytes(&mut storage_b); + + assert_eq!(a, b); + assert_eq!(&a, &b[..]); + assert_eq!(&b, &a[..]); + a.push(1u8); + assert_ne!(a, b); + assert_ne!(&a, &b[..]); + assert_ne!(&b, &a[..]); + b.push(1u8); + assert_eq!(a, b); + assert_eq!(&a, &b[..]); + assert_eq!(&b, &a[..]); + } + + #[test] + fn borrow_ish_sanity_checks() { + let mut expected = [0u8, 2, 4, 8]; + let mut storage: [MaybeUninit; 12] = unsafe { MaybeUninit::uninit().assume_init() }; + let mut fsv = FixedSliceVec::from_uninit_bytes(&mut storage[..]); + fsv.extend(expected.iter().copied()); + + assert_eq!(&expected[..], Borrow::<[u8]>::borrow(&fsv)); + assert_eq!(&mut expected[..], BorrowMut::<[u8]>::borrow_mut(&mut fsv)); + assert_eq!(&expected[..], fsv.as_ref()); + assert_eq!(&mut expected[..], fsv.as_mut()); + } + + #[test] + fn comparison_sanity_checks() { + let mut storage_a: [MaybeUninit; 2] = unsafe { MaybeUninit::uninit().assume_init() }; + let mut a: FixedSliceVec = FixedSliceVec::from_uninit_bytes(&mut storage_a); + let mut storage_b: [MaybeUninit; 2] = unsafe { MaybeUninit::uninit().assume_init() }; + let mut b: FixedSliceVec = FixedSliceVec::from_uninit_bytes(&mut storage_b); + use core::cmp::Ordering; + assert_eq!(Some(Ordering::Equal), a.partial_cmp(&b)); + b.push(1); + assert!(a.lt(&b)); + assert!(b.gt(&a)); + a.push(1); + assert!(a.ge(&b)); + assert!(a.le(&b)); + a[0] = 2; + assert!(a.gt(&b)); + assert!(b.lt(&a)); + } + + #[test] + fn insertion_sanity_checks() { + // Opposite order element addition check + let mut storage_a: [MaybeUninit; 4] = unsafe { MaybeUninit::uninit().assume_init() }; + let mut a: FixedSliceVec = FixedSliceVec::from_uninit_bytes(&mut storage_a); + let mut storage_b: [MaybeUninit; 4] = unsafe { MaybeUninit::uninit().assume_init() }; + let mut b: FixedSliceVec = FixedSliceVec::from_uninit_bytes(&mut storage_b); + assert_eq!(a.as_slice(), b.as_slice(), "Equal sets of 0 elements"); + a.insert(0usize, 1); + a.insert(0usize, 2); + a.insert(0usize, 3); + a.insert(0usize, 4); + b.push(4); + b.push(3); + b.push(2); + b.push(1); + assert_eq!( + a.as_slice(), + b.as_slice(), + "Equal sets of 4 elements, added in opposite order" + ); + assert_eq!(a.try_insert(0, 0), b.try_push(0)); + assert_eq!( + a.as_slice(), + &[4u8, 3u8, 2u8, 1u8], + "Insert should not modify until required capacity is verified." + ); + assert_eq!( + b.as_slice(), + &[4u8, 3u8, 2u8, 1u8], + "Push should not modify until required capacity is verified." + ); + + // Mixed insertion order check + a.clear(); + assert!( + a.try_insert(1usize, 55).is_err(), + "Given `n` elements and index `0` to `n-1` the maximum insert index is `n` (push)" + ); + a.insert(0usize, 4); + a.insert(1usize, 2); + a.insert(1usize, 3); + a.insert(3usize, 1); + assert_eq!( + a.as_slice(), + &[4u8, 3u8, 2u8, 1u8], + "Alternate insert order should yeild same result." + ); + + // Zero sized buffer check + let mut storage_c: [MaybeUninit; 0] = unsafe { MaybeUninit::uninit().assume_init() }; + let mut c: FixedSliceVec = FixedSliceVec::from_uninit_bytes(&mut storage_c); + assert!( + c.try_insert(0usize, 1).is_err(), + "Zero sized buffer should fail on insert" + ); + + // Zero remaining capacity check + let mut storage_d: [MaybeUninit; 1] = unsafe { MaybeUninit::uninit().assume_init() }; + let mut d: FixedSliceVec = FixedSliceVec::from_uninit_bytes(&mut storage_d); + d.push(1); + assert!( + d.try_insert(0usize, 2).is_err(), + "Zero remaining capacity should fail an insert" + ); + } +} diff --git a/src/single.rs b/src/single.rs deleted file mode 100644 index 8ca0fa2..0000000 --- a/src/single.rs +++ /dev/null @@ -1,465 +0,0 @@ -//! Functions relating to managing references to well-typed Rust values -//! stored inside arbitrary byte slices. -use core::mem::*; - -/// Plenty can go wrong when attempting to embed a value in arbitrary bytes -#[derive(Debug, PartialEq)] -pub enum EmbedValueError { - /// Difficulty generating the necessary mutable reference - /// to the embedded location. - SplitUninitError(SplitUninitError), - /// Initializing the value went wrong somehow. - ConstructionError(E), -} - -impl From for EmbedValueError { - #[inline] - fn from(e: SplitUninitError) -> Self { - EmbedValueError::SplitUninitError(e) - } -} - -/// Initialize a value into location within a provided byte slice, -/// and return a mutable reference to that value. -/// -/// The user-provided constructor function also has access to the -/// portions of the byte slice after the region allocated for -/// the embedded value itself. -/// -/// # Safety -/// -/// Panics in debug mode if destination slice's underlying -/// pointer has somehow been contrived to be null. -/// -/// This function does nothing to ensure that the embedded value will be -/// dropped when the returned reference is dropped. The caller is -/// responsible for cleaning up any side effects of the embedded value -/// outside of the destination slice. -/// -/// If the item type `T` contains any padding bytes -/// then those padding bytes may be observable in the provided slice -/// after the reference is dropped. Observing padding bytes is -/// undefined behavior. -#[inline] -pub unsafe fn embed<'a, T, F, E>( - destination: &'a mut [u8], - f: F, -) -> Result<&'a mut T, EmbedValueError> -where - F: FnOnce(&'a mut [u8]) -> Result, -{ - debug_assert!(!destination.as_ptr().is_null()); - let (_prefix, uninit_ref, suffix) = split_uninit_from_bytes(destination)?; - let ptr = uninit_ref.as_mut_ptr(); - core::ptr::write(ptr, f(suffix).map_err(EmbedValueError::ConstructionError)?); - // We literally just initialized the value, so it's safe to call it init - if let Some(ptr) = ptr.as_mut() { - Ok(ptr) - } else { - unreachable!("Just initialized the value and the pointer is based on a non-null slice") - } -} - -/// Initialize a value into location within a provided byte slice, -/// and return a mutable reference to that value. -/// -/// The user-provided constructor function also has access to the -/// portions of the byte slice after the region allocated for -/// the embedded value itself. -/// -/// # Safety -/// -/// Panics in debug mode if destination slice's underlying -/// pointer has somehow been contrived to be null. -/// -/// This function does nothing to ensure that the embedded value will be -/// dropped when the returned reference is dropped. The caller is -/// responsible for cleaning up any side effects of the embedded value -/// outside of the destination slice. -#[inline] -pub fn embed_uninit<'a, T, F, E>( - destination: &'a mut [MaybeUninit], - f: F, -) -> Result<&'a mut T, EmbedValueError> -where - F: FnOnce(&'a mut [MaybeUninit]) -> Result, -{ - debug_assert!(!destination.as_ptr().is_null()); - let (_prefix, uninit_ref, suffix) = split_uninit_from_uninit_bytes(destination)?; - unsafe { - let ptr = uninit_ref.as_mut_ptr(); - core::ptr::write(ptr, f(suffix).map_err(EmbedValueError::ConstructionError)?); - // We literally just initialized the value, so it's safe to call it init - if let Some(ptr) = ptr.as_mut() { - Ok(ptr) - } else { - unreachable!("Just initialized the value and the pointer is based on a non-null slice") - } - } -} - -/// Plenty can go wrong when attempting to find space for a value in arbitrary bytes. -#[derive(Debug, PartialEq)] -pub enum SplitUninitError { - /// Zero sized types shouldn't be placed anywhere into a byte slice anyhow. - ZeroSizedTypesUnsupported, - /// Could not calculate a valid alignment offset from the given the - /// starting point which would result in a properly-aligned value. - Unalignable, - /// Could not theoretically fit the target value into the provided byte slice - /// due to a combination of the type's alignment and size. - InsufficientSpace, -} - -/// Split out a mutable reference to an uninitialized struct at an available -/// location within a provided slice of bytes. -/// -/// Does not access or mutate the content of the provided `destination` byte -/// slice. -#[allow(clippy::type_complexity)] -#[inline] -pub fn split_uninit_from_bytes( - destination: &mut [u8], -) -> Result<(&mut [u8], &mut MaybeUninit, &mut [u8]), SplitUninitError> { - debug_assert!(!destination.as_ptr().is_null()); - // Here we rely on the assurance that MaybeUninit has the same layout - // as its parameterized type, and our knowledge of the implementation - // of `split_uninit_from_uninit_bytes`, namely that it never accesses - // or mutates any content passed to it. - let uninit_bytes = unsafe { &mut *(destination as *mut [u8] as *mut [MaybeUninit]) }; - let (prefix, uninit_ref, suffix): (_, &mut MaybeUninit, _) = - split_uninit_from_uninit_bytes(uninit_bytes)?; - let uninit_prefix = unsafe { &mut *(prefix as *mut [MaybeUninit] as *mut [u8]) }; - let uninit_ref = unsafe { transmute(uninit_ref) }; - let uninit_suffix = unsafe { &mut *(suffix as *mut [MaybeUninit] as *mut [u8]) }; - Ok((uninit_prefix, uninit_ref, uninit_suffix)) -} - -/// Split out a mutable reference to an uninitialized struct at an available -/// location within a provided slice of maybe-uninitialized bytes. -/// -/// Does not access or mutate the content of the provided `destination` byte -/// slice. -#[allow(clippy::type_complexity)] -#[inline] -pub fn split_uninit_from_uninit_bytes( - destination: &mut [MaybeUninit], -) -> Result< - ( - &mut [MaybeUninit], - &mut MaybeUninit, - &mut [MaybeUninit], - ), - SplitUninitError, -> { - debug_assert!(!destination.as_ptr().is_null()); - if size_of::() == 0 { - return Err(SplitUninitError::ZeroSizedTypesUnsupported); - } - let ptr = destination.as_mut_ptr(); - let offset = ptr.align_offset(align_of::()); - if offset == core::usize::MAX { - return Err(SplitUninitError::Unalignable); - } - if offset > destination.len() { - return Err(SplitUninitError::InsufficientSpace); - } - if let Some(end) = offset.checked_add(size_of::()) { - if end > destination.len() { - return Err(SplitUninitError::InsufficientSpace); - } - } else { - return Err(SplitUninitError::InsufficientSpace); - } - let (prefix, rest) = destination.split_at_mut(offset); - let (middle, suffix) = rest.split_at_mut(size_of::()); - let maybe_uninit = middle.as_mut_ptr() as *mut MaybeUninit; - let maybe_uninit = if let Some(maybe_uninit) = unsafe { maybe_uninit.as_mut() } { - maybe_uninit - } else { - unreachable!("Should be non-null since we rely on the input byte slice being non-null.") - }; - Ok((prefix, maybe_uninit, suffix)) -} - -#[cfg(test)] -#[allow(dead_code)] -mod tests { - use super::*; - #[derive(PartialEq)] - struct ZST; - - #[derive(Default)] - struct TooBig { - colossal: [Colossal; 32], - } - #[derive(Default)] - struct Colossal { - huge: [Huge; 32], - } - #[derive(Default)] - struct Huge { - large: [Large; 32], - } - #[derive(Default)] - struct Large { - medium: [u64; 32], - } - - #[test] - fn zero_sized_types_not_permitted() { - let mut bytes = [0u8; 64]; - if let Err(e) = split_uninit_from_bytes::(&mut bytes[..]) { - assert_eq!(SplitUninitError::ZeroSizedTypesUnsupported, e); - } else { - unreachable!("Expected an err"); - } - if let Err(e) = unsafe { embed(&mut bytes[..], |_| -> Result { Ok(ZST) }) } { - assert_eq!( - EmbedValueError::SplitUninitError(SplitUninitError::ZeroSizedTypesUnsupported), - e - ); - } else { - unreachable!("Expected an err"); - } - - let mut uninit_bytes: [MaybeUninit; 64] = - unsafe { MaybeUninit::uninit().assume_init() }; - if let Err(e) = split_uninit_from_uninit_bytes::(&mut uninit_bytes[..]) { - assert_eq!(SplitUninitError::ZeroSizedTypesUnsupported, e); - } else { - unreachable!("Expected an err"); - } - if let Err(e) = embed_uninit(&mut uninit_bytes[..], |_| -> Result { Ok(ZST) }) { - assert_eq!( - EmbedValueError::SplitUninitError(SplitUninitError::ZeroSizedTypesUnsupported), - e - ); - } else { - unreachable!("Expected an err"); - } - } - - #[test] - fn split_not_enough_space_detected() { - let mut bytes = [0u8; 64]; - if let Err(e) = split_uninit_from_bytes::(&mut bytes[..]) { - match e { - SplitUninitError::InsufficientSpace | SplitUninitError::Unalignable => (), - _ => unreachable!("Unexpected error kind"), - } - } else { - unreachable!("Expected an err"); - } - } - - #[test] - fn split_uninit_not_enough_space_detected() { - let mut uninit_bytes: [MaybeUninit; 64] = - unsafe { MaybeUninit::uninit().assume_init() }; - if let Err(e) = split_uninit_from_uninit_bytes::(&mut uninit_bytes[..]) { - match e { - SplitUninitError::InsufficientSpace | SplitUninitError::Unalignable => (), - _ => unreachable!("Unexpected error kind"), - } - } else { - unreachable!("Expected an err"); - } - } - - #[test] - fn split_uninit_from_bytes_observe_leftovers() { - let mut bytes = [0u8; 61]; - match split_uninit_from_bytes::<[u16; 3]>(&mut bytes[..]) { - Ok((prefix, mid, suffix)) => { - *mid = MaybeUninit::new([3, 4, 5]); - for v in prefix { - assert_eq!(0, *v); - } - for v in suffix { - assert_eq!(0, *v); - } - } - Err(SplitUninitError::Unalignable) => return (), // Most likely MIRI messing with align-ability - Err(e) => unreachable!("Unexpected error: {:?}", e), - } - } - - #[test] - fn split_uninit_from_uninit_bytes_observe_leftovers() { - let mut bytes: [MaybeUninit; 64] = unsafe { MaybeUninit::uninit().assume_init() }; - match split_uninit_from_uninit_bytes::<[u16; 3]>(&mut bytes[..]) { - Ok((prefix, mid, suffix)) => { - *mid = MaybeUninit::new([3, 4, 5]); - let had_prefix = prefix.len() > 0; - let had_suffix = suffix.len() > 0; - assert!(had_prefix | had_suffix); - } - Err(SplitUninitError::Unalignable) => return (), // Most likely MIRI messing with align-ability - Err(e) => unreachable!("Unexpected error: {:?}", e), - } - } - - #[test] - fn split_uninit_from_bytes_empty() { - let bytes: &mut [u8] = &mut []; - assert_eq!( - SplitUninitError::InsufficientSpace, - split_uninit_from_bytes::<[u16; 3]>(bytes).unwrap_err() - ); - } - - #[test] - fn split_uninit_from_uninit_bytes_empty() { - let bytes: &mut [MaybeUninit] = &mut []; - assert_eq!( - SplitUninitError::InsufficientSpace, - split_uninit_from_uninit_bytes::<[u16; 3]>(bytes).unwrap_err() - ); - } - - #[test] - fn embed_not_enough_space_detected() { - let mut bytes = [0u8; 64]; - if let Err(e) = unsafe { - embed(&mut bytes[..], |_| -> Result { - unreachable!("Don't expect this to execute since we can tell from the types that there is not enough space") - }) - } { - match e { - EmbedValueError::SplitUninitError(SplitUninitError::InsufficientSpace) - | EmbedValueError::SplitUninitError(SplitUninitError::Unalignable) => (), - _ => unreachable!("Unexpected error kind"), - } - } else { - unreachable!("Expected an err"); - } - } - - #[test] - fn embed_uninit_not_enough_space_detected() { - let mut uninit_bytes: [MaybeUninit; 64] = - unsafe { MaybeUninit::uninit().assume_init() }; - if let Err(e) = embed_uninit(&mut uninit_bytes[..], |_| -> Result { - unreachable!("Don't expect this to execute since we can tell from the types that there is not enough space") - }) { - match e { - EmbedValueError::SplitUninitError(SplitUninitError::InsufficientSpace) - | EmbedValueError::SplitUninitError(SplitUninitError::Unalignable) => (), - _ => unreachable!("Unexpected error kind"), - } - } else { - unreachable!("Expected an err"); - } - } - - #[test] - fn happy_path_split() { - let mut bytes = [0u8; 512]; - let (prefix, _large_ref, suffix) = match split_uninit_from_bytes::(&mut bytes[..]) { - Ok(r) => r, - Err(SplitUninitError::Unalignable) => return (), // Most likely MIRI messing with align-ability - Err(e) => unreachable!("Unexpected error: {:?}", e), - }; - assert_eq!( - prefix.len() + core::mem::size_of::() + suffix.len(), - bytes.len() - ); - } - - #[test] - fn happy_path_split_uninit() { - let mut uninit_bytes: [MaybeUninit; 512] = - unsafe { MaybeUninit::uninit().assume_init() }; - let (prefix, _large_ref, suffix) = - match split_uninit_from_uninit_bytes::(&mut uninit_bytes[..]) { - Ok(r) => r, - Err(SplitUninitError::Unalignable) => return (), // Most likely MIRI messing with align-ability - Err(e) => unreachable!("Unexpected error: {:?}", e), - }; - assert_eq!( - prefix.len() + core::mem::size_of::() + suffix.len(), - uninit_bytes.len() - ); - } - - #[test] - fn happy_path_embed() { - const BACKING_BYTES_MAX_SIZE: usize = 512; - let mut bytes = [2u8; BACKING_BYTES_MAX_SIZE]; - let large_ref = match unsafe { - embed(&mut bytes[..], |b| -> Result { - assert!(b.iter().all(|b| *b == 2)); - let mut l = Large::default(); - l.medium[0] = 3; - l.medium[1] = 1; - l.medium[2] = 4; - Ok(l) - }) - } { - Ok(r) => r, - Err(EmbedValueError::SplitUninitError(SplitUninitError::Unalignable)) => return (), // Most likely MIRI messing with align-ability - Err(e) => unreachable!("Unexpected error: {:?}", e), - }; - - assert_eq!(3, large_ref.medium[0]); - assert_eq!(1, large_ref.medium[1]); - assert_eq!(4, large_ref.medium[2]); - } - #[test] - fn happy_path_embed_uninit() { - const BACKING_BYTES_MAX_SIZE: usize = 512; - let mut uninit_bytes: [MaybeUninit; BACKING_BYTES_MAX_SIZE] = - unsafe { MaybeUninit::uninit().assume_init() }; - let large_ref = match embed_uninit(&mut uninit_bytes[..], |_| -> Result { - let mut l = Large::default(); - l.medium[0] = 3; - l.medium[1] = 1; - l.medium[2] = 4; - Ok(l) - }) { - Ok(r) => r, - Err(EmbedValueError::SplitUninitError(SplitUninitError::Unalignable)) => return (), // Most likely MIRI messing with align-ability - Err(e) => unreachable!("Unexpected error: {:?}", e), - }; - assert_eq!(3, large_ref.medium[0]); - assert_eq!(1, large_ref.medium[1]); - assert_eq!(4, large_ref.medium[2]); - } - #[test] - fn embed_does_not_run_drops() { - let mut storage: [u8; 16] = [0u8; 16]; - #[derive(Debug)] - struct Target(bool); - impl Drop for Target { - fn drop(&mut self) { - self.0 = true; - } - } - let emb = unsafe { - embed(&mut storage[..], move |_leftovers| { - Result::::Ok(Target(false)) - }) - .unwrap() - }; - - assert!(!emb.0); - } - #[test] - fn embed_uninit_does_not_run_drops() { - let mut storage: [MaybeUninit; 16] = unsafe { MaybeUninit::uninit().assume_init() }; - #[derive(Debug)] - struct Target(bool); - impl Drop for Target { - fn drop(&mut self) { - self.0 = true; - } - } - let emb = embed_uninit(&mut storage[..], move |_leftovers| { - Result::::Ok(Target(false)) - }) - .unwrap(); - - assert!(!emb.0); - } -} diff --git a/src/vec.rs b/src/vec.rs deleted file mode 100644 index c90af9f..0000000 --- a/src/vec.rs +++ /dev/null @@ -1,981 +0,0 @@ -//! FixedSliceVec is a structure for defining variably populated vectors backed -//! by a slice of storage capacity. -use core::borrow::{Borrow, BorrowMut}; -use core::convert::From; -use core::hash::{Hash, Hasher}; -use core::iter::Extend; -use core::mem::MaybeUninit; -use core::ops::{Deref, DerefMut}; - -/// Vec-like structure backed by a storage slice of possibly uninitialized data. -/// -/// The maximum length (the capacity) is fixed at runtime to the length of the -/// provided storage slice. -pub struct FixedSliceVec<'a, T: Sized> { - /// Backing storage, provides capacity - storage: &'a mut [MaybeUninit], - /// The number of items that have been - /// initialized - len: usize, -} - -impl<'a, T: Sized> Drop for FixedSliceVec<'a, T> { - fn drop(&mut self) { - self.clear(); - } -} - -impl<'a, T: Sized> FixedSliceVec<'a, T> { - /// Create a FixedSliceVec backed by a slice of possibly-uninitialized data. - /// The backing storage slice is used as capacity for Vec-like operations, - /// - /// The initial length of the FixedSliceVec is 0. - #[inline] - pub fn new(storage: &'a mut [MaybeUninit]) -> Self { - FixedSliceVec { storage, len: 0 } - } - - /// Create a well-aligned FixedSliceVec backed by a slice of the provided bytes. - /// The slice is as large as possible given the item type and alignment of - /// the provided bytes. - /// - /// If you are interested in recapturing the prefix and suffix bytes on - /// either side of the carved-out FixedSliceVec buffer, consider using `align_from_bytes` instead: - /// - /// ``` - /// # let mut bytes = [3u8, 1, 4, 1, 5, 9]; - /// let vec = unsafe { fixed_slice_vec::FixedSliceVec::from_bytes(&mut bytes[..]) }; - /// # let vec: fixed_slice_vec::FixedSliceVec = vec; - /// ``` - /// - /// The bytes are treated as if they might be uninitialized, so even if `T` is `u8`, - /// the length of the returned `FixedSliceVec` will be zero. - /// - /// # Safety - /// - /// If the item type `T` of the FixedSliceVec contains any padding bytes - /// then those padding bytes may be observable in the provided slice - /// after the `FixedSliceVec` is dropped. Observing padding bytes is - /// undefined behavior. - #[inline] - pub unsafe fn from_bytes(bytes: &'a mut [u8]) -> FixedSliceVec<'a, T> { - FixedSliceVec::align_from_bytes(bytes).1 - } - - /// Create a well-aligned FixedSliceVec backed by a slice of the provided - /// uninitialized bytes. The typed slice is as large as possible given its - /// item type and the alignment of the provided bytes. - /// - /// If you are interested in recapturing the prefix and suffix bytes on - /// either side of the carved-out FixedSliceVec buffer, consider using `align_from_uninit_bytes`: - /// - #[inline] - pub fn from_uninit_bytes(bytes: &'a mut [MaybeUninit]) -> FixedSliceVec<'a, T> { - FixedSliceVec::align_from_uninit_bytes(bytes).1 - } - - /// Create a well-aligned FixedSliceVec backed by a slice of the provided bytes. - /// The slice is as large as possible given the item type and alignment of - /// the provided bytes. Returns the unused prefix and suffix bytes on - /// either side of the carved-out FixedSliceVec. - /// - /// ``` - /// let mut bytes = [3u8, 1, 4, 1, 5, 9]; - /// let (prefix, vec, suffix) = unsafe { fixed_slice_vec::FixedSliceVec::align_from_bytes(&mut bytes[..]) }; - /// let vec: fixed_slice_vec::FixedSliceVec = vec; - /// ``` - /// - /// The bytes are treated as if they might be uninitialized, so even if `T` is `u8`, - /// the length of the returned `FixedSliceVec` will be zero. - /// - /// # Safety - /// - /// If the item type `T` of the FixedSliceVec contains any padding bytes - /// then those padding bytes may be observable in the provided slice - /// after the `FixedSliceVec` is dropped. Observing padding bytes is - /// undefined behavior. - #[inline] - pub unsafe fn align_from_bytes( - bytes: &'a mut [u8], - ) -> (&'a mut [u8], FixedSliceVec<'a, T>, &'a mut [u8]) { - let (prefix, storage, suffix) = bytes.align_to_mut(); - (prefix, FixedSliceVec { storage, len: 0 }, suffix) - } - - /// Create a well-aligned FixedSliceVec backed by a slice of the provided bytes. - /// The slice is as large as possible given the item type and alignment of - /// the provided bytes. Returns the unused prefix and suffix bytes on - /// either side of the carved-out FixedSliceVec. - /// - /// ``` - /// # use core::mem::MaybeUninit; - /// let mut bytes: [MaybeUninit; 15] = unsafe { MaybeUninit::uninit().assume_init() }; - /// let (prefix, vec, suffix) = fixed_slice_vec::FixedSliceVec::align_from_uninit_bytes(&mut - /// bytes[..]); - /// let vec: fixed_slice_vec::FixedSliceVec = vec; - /// ``` - /// - /// The length of the returned `FixedSliceVec` will be zero. - #[inline] - pub fn align_from_uninit_bytes( - bytes: &'a mut [MaybeUninit], - ) -> ( - &'a mut [MaybeUninit], - FixedSliceVec<'a, T>, - &'a mut [MaybeUninit], - ) { - let (prefix, storage, suffix) = unsafe { bytes.align_to_mut() }; - (prefix, FixedSliceVec { storage, len: 0 }, suffix) - } - - /// Returns an unsafe mutable pointer to the FixedSliceVec's buffer. - /// - /// The caller must ensure that the FixedSliceVec and the backing - /// storage data for the FixedSliceVec (provided at construction) - /// outlives the pointer this function returns. - /// - /// Furthermore, the contents of the buffer are not guaranteed - /// to have been initialized at indices >= len. - /// - /// # Examples - /// - /// ``` - /// use core::mem::MaybeUninit; - /// let mut storage: [MaybeUninit; 16] = unsafe { MaybeUninit::uninit().assume_init() }; - /// let mut x: fixed_slice_vec::FixedSliceVec = fixed_slice_vec::FixedSliceVec::from_uninit_bytes(&mut storage[..]); - /// assert!(x.try_extend([1u16, 2, 4, 8].iter().copied()).is_ok()); - /// let size = x.len(); - /// let x_ptr = x.as_mut_ptr(); - /// - /// // Set elements via raw pointer writes. - /// unsafe { - /// for i in 0..size { - /// *x_ptr.add(i) = MaybeUninit::new(i as u16); - /// } - /// } - /// assert_eq!(&*x, &[0,1,2,3]); - /// ``` - #[inline] - pub fn as_mut_ptr(&mut self) -> *mut MaybeUninit { - self.storage.as_mut_ptr() - } - /// Returns a raw pointer to the FixedSliceVec's buffer. - /// - /// The caller must ensure that the FixedSliceVec and the backing - /// storage data for the FixedSliceVec (provided at construction) - /// outlives the pointer this function returns. - /// - /// Furthermore, the contents of the buffer are not guaranteed - /// to have been initialized at indices >= len. - /// - /// The caller must also ensure that the memory the pointer (non-transitively) points to - /// is never written to using this pointer or any pointer derived from it. - /// - /// If you need to mutate the contents of the slice with pointers, use [`as_mut_ptr`]. - /// - /// # Examples - /// - /// ``` - /// use core::mem::MaybeUninit; - /// let mut storage: [MaybeUninit; 16] = unsafe { MaybeUninit::uninit().assume_init() }; - /// let mut x: fixed_slice_vec::FixedSliceVec = fixed_slice_vec::FixedSliceVec::from_uninit_bytes(&mut storage[..]); - /// x.extend([1u16, 2, 4].iter().copied()); - /// let x_ptr = x.as_ptr(); - /// - /// unsafe { - /// for i in 0..x.len() { - /// assert_eq!((*x_ptr.add(i)).assume_init(), 1 << i); - /// } - /// } - /// ``` - /// - /// [`as_mut_ptr`]: #method.as_mut_ptr - #[inline] - pub fn as_ptr(&self) -> *const MaybeUninit { - self.storage.as_ptr() - } - - /// The length of the FixedSliceVec. The number of initialized - /// values that have been added to it. - #[inline] - pub fn len(&self) -> usize { - self.len - } - - /// The maximum amount of items that can live in this FixedSliceVec - #[inline] - pub fn capacity(&self) -> usize { - self.storage.len() - } - - /// Returns true if there are no items present. - #[inline] - pub fn is_empty(&self) -> bool { - self.len == 0 - } - - /// Returns true if the FixedSliceVec is full to capacity. - #[inline] - pub fn is_full(&self) -> bool { - self.len == self.capacity() - } - - /// Attempt to add a value to the FixedSliceVec. - /// - /// Returns an error if there is not enough capacity to hold another item. - #[inline] - pub fn try_push(&mut self, value: T) -> Result<(), StorageError> { - if self.is_full() { - return Err(StorageError(value)); - } - self.storage[self.len] = MaybeUninit::new(value); - self.len += 1; - Ok(()) - } - - /// Attempt to add a value to the FixedSliceVec. - /// - /// # Panics - /// - /// Panics if there is not sufficient capacity to hold another item. - #[inline] - pub fn push(&mut self, value: T) { - self.try_push(value).unwrap(); - } - - /// Attempt to insert a value to the FixedSliceVec. - /// - /// Returns an error if there is not enough capacity to hold another item. - #[inline] - pub fn try_insert(&mut self, index: usize, value: T) -> Result<(), StorageError> { - if index > self.len() { - return Err(StorageError(value)); - } - self.try_push(value)?; - self.as_mut_slice()[index..].rotate_right(1); - Ok(()) - } - - /// Inserts a value to the FixedSliceVec. - /// - /// # Panics - /// - /// Panics if there is not sufficient capacity to hold another item. - #[inline] - pub fn insert(&mut self, index: usize, value: T) { - self.try_insert(index, value).unwrap() - } - - /// Attempt to add as many values as will fit from an iterable. - /// - /// Returns Ok(()) if all of the items in the iterator can fit into `self`. - /// Returns an Err containing the iterator if `self` fills up and there - /// are items remaining in the iterator. - #[inline] - pub fn try_extend( - &mut self, - iterable: impl IntoIterator, - ) -> Result<(), impl Iterator> { - let mut iter = iterable.into_iter().peekable(); - loop { - if iter.peek().is_some() { - if self.is_full() { - return Err(iter); - } else if let Some(item) = iter.next() { - self.storage[self.len] = MaybeUninit::new(item); - self.len += 1; - } else { - unreachable!("`FixedSliceVec::try_extend` peeked above to ensure that `next` would return Some") - } - } else { - return Ok(()); - } - } - } - - /// Remove the last item from the FixedSliceVec. - #[inline] - pub fn pop(&mut self) -> Option { - if self.len == 0 { - return None; - } - self.len -= 1; - Some(unsafe { self.storage[self.len].as_ptr().read() }) - } - - /// Removes the FixedSliceVec's tracking of all items in it while retaining the - /// same capacity. - #[inline] - pub fn clear(&mut self) { - let original_len = self.len; - self.len = 0; - unsafe { - // Note we cannot use the usual DerefMut helper to produce a slice because it relies - // on the `len` field, which we have updated above already. - // The early setting of `len` is designed to avoid double-free errors if there - // is a panic in the middle of the following drop. - (core::slice::from_raw_parts_mut(self.storage.as_mut_ptr() as *mut T, original_len) - as *mut [T]) - .drop_in_place(); - } - } - - /// Shortens the FixedSliceVec, keeping the first `len` elements and dropping the rest. - /// - /// If len is greater than the current length, this has no effect. - /// Note that this method has no effect on the capacity of the FixedSliceVec. - #[inline] - pub fn truncate(&mut self, len: usize) { - let original_len = self.len; - if len > original_len { - return; - } - self.len = len; - unsafe { - // Note we cannot use the usual DerefMut helper to produce a slice because it relies - // on the `len` field, which we have updated above already. - // The early setting of `len` is designed to avoid double-free errors if there - // is a panic in the middle of the following drop. - (&mut core::slice::from_raw_parts_mut(self.storage.as_mut_ptr() as *mut T, original_len)[len..] as *mut [T]).drop_in_place(); - } - } - /// Removes and returns the element at position `index` within the FixedSliceVec, - /// shifting all elements after it to the left. - /// - /// # Panics - /// - /// Panics if `index` is out of bounds. - pub fn remove(&mut self, index: usize) -> T { - // Error message and overall impl strategy following along with std vec, - if index >= self.len { - panic!( - "removal index (is {}) should be < len (is {})", - index, self.len - ); - } - unsafe { self.unchecked_remove(index) } - } - - /// Removes and returns the element at position `index` within the FixedSliceVec, - /// shifting all elements after it to the left. - pub fn try_remove(&mut self, index: usize) -> Result { - if index >= self.len { - return Err(IndexError); - } - Ok(unsafe { self.unchecked_remove(index) }) - } - - /// Remove and return an element without checking if it's actually there. - #[inline] - unsafe fn unchecked_remove(&mut self, index: usize) -> T { - let ptr = (self.as_mut_ptr() as *mut T).add(index); - let out = core::ptr::read(ptr); - core::ptr::copy(ptr.offset(1), ptr, self.len - index - 1); - self.len -= 1; - out - } - /// Removes an element from the vector and returns it. - /// - /// The removed element is replaced by the last element of the vector. - /// - /// This does not preserve ordering, but is O(1). - /// - /// # Panics - /// - /// Panics if `index` is out of bounds. - pub fn swap_remove(&mut self, index: usize) -> T { - if index >= self.len { - panic!( - "swap_remove index (is {}) should be < len (is {})", - index, self.len - ); - } - unsafe { self.unchecked_swap_remove(index) } - } - /// Removes an element from the vector and returns it. - /// - /// The removed element is replaced by the last element of the vector. - /// - /// This does not preserve ordering, but is O(1). - pub fn try_swap_remove(&mut self, index: usize) -> Result { - if index >= self.len { - return Err(IndexError); - } - Ok(unsafe { self.unchecked_swap_remove(index) }) - } - - /// swap_remove, without the length-checking - #[inline] - unsafe fn unchecked_swap_remove(&mut self, index: usize) -> T { - let target_ptr = (self.as_mut_ptr() as *mut T).add(index); - let end_ptr = (self.as_ptr() as *const T).add(self.len - 1); - let end_value = core::ptr::read(end_ptr); - self.len -= 1; - core::ptr::replace(target_ptr, end_value) - } - - /// Obtain an immutable slice view on the initialized portion of the - /// FixedSliceVec. - #[inline] - pub fn as_slice(&self) -> &[T] { - Deref::deref(self) - } - - /// Obtain a mutable slice view on the initialized portion of the - /// FixedSliceVec. - #[inline] - pub fn as_mut_slice(&mut self) -> &mut [T] { - DerefMut::deref_mut(self) - } -} - -/// Error that occurs when a call that attempts to increase -/// the number of items in the FixedSliceVec fails -/// due to insufficient storage capacity. -#[derive(Clone, PartialEq, PartialOrd, Eq, Ord)] -pub struct StorageError(pub T); - -impl core::fmt::Debug for StorageError { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { - f.write_str("Push failed because FixedSliceVec was full") - } -} - -/// Error that occurs when a call that attempts to access -/// the FixedSliceVec in a manner that does not respect -/// the current length of the vector, i.e. its current -/// number of initialized items. -#[derive(Clone, PartialEq, PartialOrd, Eq, Ord)] -pub struct IndexError; - -impl core::fmt::Debug for IndexError { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { - f.write_str( - "Access to the FixedSliceVec failed because an invalid index or length was provided", - ) - } -} - -impl<'a, T: Sized> From<&'a mut [MaybeUninit]> for FixedSliceVec<'a, T> { - #[inline] - fn from(v: &'a mut [MaybeUninit]) -> Self { - FixedSliceVec { storage: v, len: 0 } - } -} - -impl<'a, T: Sized> Hash for FixedSliceVec<'a, T> -where - T: Hash, -{ - #[inline] - fn hash(&self, state: &mut H) { - Hash::hash(&**self, state) - } -} - -impl<'a, T: Sized> PartialEq for FixedSliceVec<'a, T> -where - T: PartialEq, -{ - #[inline] - fn eq(&self, other: &Self) -> bool { - **self == **other - } -} - -impl<'a, T: Sized> PartialEq<[T]> for FixedSliceVec<'a, T> -where - T: PartialEq, -{ - #[inline] - fn eq(&self, other: &[T]) -> bool { - **self == *other - } -} - -impl<'a, T: Sized> Eq for FixedSliceVec<'a, T> where T: Eq {} - -impl<'a, T: Sized> Borrow<[T]> for FixedSliceVec<'a, T> { - #[inline] - fn borrow(&self) -> &[T] { - self - } -} - -impl<'a, T: Sized> BorrowMut<[T]> for FixedSliceVec<'a, T> { - #[inline] - fn borrow_mut(&mut self) -> &mut [T] { - self - } -} - -impl<'a, T: Sized> AsRef<[T]> for FixedSliceVec<'a, T> { - #[inline] - fn as_ref(&self) -> &[T] { - self - } -} - -impl<'a, T: Sized> AsMut<[T]> for FixedSliceVec<'a, T> { - #[inline] - fn as_mut(&mut self) -> &mut [T] { - self - } -} - -impl<'a, T: Sized> core::fmt::Debug for FixedSliceVec<'a, T> -where - T: core::fmt::Debug, -{ - #[inline] - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - (**self).fmt(f) - } -} - -impl<'a, T: Sized> PartialOrd for FixedSliceVec<'a, T> -where - T: PartialOrd, -{ - #[inline] - fn partial_cmp(&self, other: &FixedSliceVec<'a, T>) -> Option { - (**self).partial_cmp(other) - } - - #[inline] - fn lt(&self, other: &Self) -> bool { - (**self).lt(other) - } - - #[inline] - fn le(&self, other: &Self) -> bool { - (**self).le(other) - } - - #[inline] - fn gt(&self, other: &Self) -> bool { - (**self).gt(other) - } - - #[inline] - fn ge(&self, other: &Self) -> bool { - (**self).ge(other) - } -} - -impl<'a, T: Sized> Deref for FixedSliceVec<'a, T> { - type Target = [T]; - #[inline] - fn deref(&self) -> &Self::Target { - unsafe { core::slice::from_raw_parts(self.storage.as_ptr() as *const T, self.len) } - } -} - -impl<'a, T: Sized> DerefMut for FixedSliceVec<'a, T> { - #[inline] - fn deref_mut(&mut self) -> &mut [T] { - unsafe { core::slice::from_raw_parts_mut(self.storage.as_mut_ptr() as *mut T, self.len) } - } -} - -/// Adds as many items from the provided iterable as can fit. -/// -/// Gives no indication of how many were extracted or if some -/// could not fit. -/// -/// Use `FixedSliceVec::try_extend` if you require more fine- -/// grained signal about the outcome of attempted extension. -impl<'a, T: Sized> Extend for FixedSliceVec<'a, T> { - fn extend>(&mut self, iter: I) { - let _ = self.try_extend(iter); - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn from_uninit() { - let mut data: [MaybeUninit; 32] = unsafe { MaybeUninit::uninit().assume_init() }; - let mut sv: FixedSliceVec = (&mut data[..]).into(); - assert_eq!(0, sv.len()); - assert_eq!(32, sv.capacity()); - assert!(sv.is_empty()); - let sv_as_slice: &[u8] = &sv; - let empty_slice: &[u8] = &[]; - assert_eq!(empty_slice, sv_as_slice); - assert_eq!(Ok(()), sv.try_push(3)); - assert_eq!(Ok(()), sv.try_push(1)); - assert_eq!(Ok(()), sv.try_push(4)); - let non_empty_slice: &[u8] = &[3u8, 1, 4]; - assert_eq!(non_empty_slice, &sv as &[u8]); - let sv_as_mut_slice: &mut [u8] = &mut sv; - sv_as_mut_slice[1] = 2; - let non_empty_slice: &[u8] = &[3u8, 2, 4]; - assert_eq!(non_empty_slice, &sv as &[u8]); - - sv.clear(); - assert_eq!(0, sv.len()); - assert!(sv.is_empty()); - } - - #[test] - fn happy_path_from_bytes() { - let mut data = [0u8; 31]; - let mut sv: FixedSliceVec = unsafe { FixedSliceVec::from_bytes(&mut data[..]) }; - assert!(sv.is_empty()); - // capacity might be 0 if miri messes with the align-ability of pointers - if sv.capacity() > 0 { - for i in 0..sv.capacity() { - assert_eq!(Ok(()), sv.try_push(i)); - } - } - assert!(sv.is_full()); - } - - #[test] - fn align_captures_suffix_and_prefix() { - let mut data = [ - 3u8, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9, 3, 2, 3, 8, 4, 6, 2, 6, 4, 3, 3, - ]; - let original_len = data.len(); - for i in 0..original_len { - for len in 0..original_len - i { - let storage = &mut data[i..i + len]; - let storage_len = storage.len(); - let (prefix, fixed_slice_vec, suffix): (_, FixedSliceVec, _) = - unsafe { FixedSliceVec::align_from_bytes(storage) }; - assert_eq!( - storage_len, - prefix.len() + 2 * fixed_slice_vec.capacity() + suffix.len() - ); - } - } - } - fn uninit_storage() -> [MaybeUninit; 4] { - unsafe { MaybeUninit::uninit().assume_init() } - } - - #[test] - fn as_ptr_reveals_expected_internal_content() { - let expected = [0u8, 1, 2, 3]; - let mut storage = uninit_storage(); - let mut fsv = FixedSliceVec::from_uninit_bytes(&mut storage[..]); - assert!(fsv.try_extend(expected.iter().copied()).is_ok()); - - let ptr = fsv.as_ptr(); - for i in 0..fsv.len() { - assert_eq!(expected[i], unsafe { (*ptr.add(i)).assume_init() }); - } - - let mut fsv = fsv; - fsv[3] = 99; - assert_eq!(99, unsafe { (*fsv.as_ptr().add(3)).assume_init() }) - } - - #[test] - fn as_mut_ptr_allows_changes_to_internal_content() { - let expected = [0u8, 2, 4, 8]; - let mut storage = uninit_storage(); - let mut fsv: FixedSliceVec = FixedSliceVec::from_uninit_bytes(&mut storage[..]); - assert!(fsv.try_extend(expected.iter().copied()).is_ok()); - - assert_eq!(8, unsafe { fsv.as_mut_ptr().add(3).read().assume_init() }); - unsafe { - fsv.as_mut_ptr().add(3).write(MaybeUninit::new(99)); - } - assert_eq!(99, fsv[3]); - - fsv[1] = 200; - assert_eq!(200, unsafe { fsv.as_mut_ptr().add(1).read().assume_init() }); - } - - #[test] - fn manual_truncate() { - let expected = [0u8, 2, 4, 8]; - let mut storage = uninit_storage(); - let mut fsv = FixedSliceVec::from_uninit_bytes(&mut storage[..]); - assert!(fsv.try_extend(expected.iter().copied()).is_ok()); - - fsv.truncate(100); - assert_eq!(&[0u8, 2, 4, 8], fsv.as_slice()); - fsv.truncate(2); - assert_eq!(&[0u8, 2], fsv.as_slice()); - fsv.truncate(2); - assert_eq!(&[0u8, 2], fsv.as_slice()); - fsv.truncate(0); - assert!(fsv.is_empty()); - } - - #[test] - fn manual_try_remove() { - let expected = [0u8, 2, 4, 8]; - let mut storage = uninit_storage(); - let mut fsv = FixedSliceVec::from_uninit_bytes(&mut storage[..]); - assert!(fsv.try_extend(expected.iter().copied()).is_ok()); - - assert_eq!(Err(IndexError), fsv.try_remove(100)); - assert_eq!(Err(IndexError), fsv.try_remove(4)); - assert_eq!(&[0u8, 2, 4, 8], fsv.as_slice()); - assert_eq!(Ok(2), fsv.try_remove(1)); - assert_eq!(&[0u8, 4, 8], fsv.as_slice()); - } - - #[test] - #[should_panic] - fn manual_swap_remove_outside_range() { - let mut storage = uninit_storage(); - let mut fsv = FixedSliceVec::from_uninit_bytes(&mut storage[..]); - assert!(fsv.try_extend([0u8, 2, 4, 8].iter().copied()).is_ok()); - fsv.swap_remove(100); - } - - #[test] - #[should_panic] - fn manual_swap_remove_empty() { - let mut storage = uninit_storage(); - let mut fsv: FixedSliceVec = FixedSliceVec::from_uninit_bytes(&mut storage[..]); - assert!(fsv.capacity() > 0); - assert_eq!(0, fsv.len()); - fsv.swap_remove(0); - } - - #[test] - fn manual_swap_remove_inside_range() { - let mut storage = uninit_storage(); - let mut fsv = FixedSliceVec::from_uninit_bytes(&mut storage[..]); - assert!(fsv.try_extend([0u8, 2, 4, 8].iter().copied()).is_ok()); - assert_eq!(&[0u8, 2, 4, 8], fsv.as_slice()); - assert_eq!(2, fsv.swap_remove(1)); - assert_eq!(&[0u8, 8, 4], fsv.as_slice()); - assert_eq!(0, fsv.swap_remove(0)); - assert_eq!(&[4u8, 8], fsv.as_slice()); - assert_eq!(4, fsv.swap_remove(0)); - assert_eq!(&[8u8], fsv.as_slice()); - assert_eq!(8, fsv.swap_remove(0)); - assert!(fsv.as_slice().is_empty()); - } - - #[test] - fn manual_try_swap_remove() { - let expected = [0u8, 2, 4, 8]; - let mut storage = uninit_storage(); - let mut fsv = FixedSliceVec::from_uninit_bytes(&mut storage[..]); - assert!(fsv.try_extend(expected.iter().copied()).is_ok()); - - assert_eq!(Err(IndexError), fsv.try_swap_remove(100)); - assert_eq!(Err(IndexError), fsv.try_swap_remove(4)); - assert_eq!(&[0u8, 2, 4, 8], fsv.as_slice()); - assert_eq!(Ok(2), fsv.try_swap_remove(1)); - assert_eq!(&[0u8, 8, 4], fsv.as_slice()); - assert_eq!(Ok(0), fsv.try_swap_remove(0)); - assert_eq!(&[4u8, 8], fsv.as_slice()); - assert_eq!(Ok(4), fsv.try_swap_remove(0)); - assert_eq!(&[8u8], fsv.as_slice()); - assert_eq!(Ok(8), fsv.try_swap_remove(0)); - assert!(fsv.as_slice().is_empty()); - assert_eq!(Err(IndexError), fsv.try_swap_remove(0)); - } - - #[test] - fn try_extend_with_exactly_enough_room() { - let expected = [0u8, 2, 4, 8]; - let mut storage = uninit_storage(); - let mut fsv = FixedSliceVec::from_uninit_bytes(&mut storage[..]); - assert!(fsv.try_extend(expected.iter().copied()).is_ok()); - assert_eq!(&expected[..], &fsv[..]); - } - - #[test] - fn try_extend_with_more_than_enough_room() { - let expected = [0u8, 2, 4, 8]; - let mut storage: [MaybeUninit; 100] = unsafe { MaybeUninit::uninit().assume_init() }; - let mut fsv = FixedSliceVec::from_uninit_bytes(&mut storage[..]); - assert!(fsv.try_extend(expected.iter().copied()).is_ok()); - assert_eq!(&expected[..], &fsv[..]); - } - - #[test] - fn try_extend_with_not_enough_room() { - let expected = [0u8, 2, 4, 8]; - let mut storage: [MaybeUninit; 2] = unsafe { MaybeUninit::uninit().assume_init() }; - let mut fsv = FixedSliceVec::from_uninit_bytes(&mut storage[..]); - let mut out_iter = fsv.try_extend(expected.iter().copied()).unwrap_err(); - assert_eq!(Some(4), out_iter.next()); - assert_eq!(Some(8), out_iter.next()); - assert_eq!(None, out_iter.next()); - assert_eq!(&expected[0..2], &fsv[..]); - } - #[test] - fn extend_with_exactly_enough_room() { - let expected = [0u8, 2, 4, 8]; - let mut storage = uninit_storage(); - let mut fsv = FixedSliceVec::from_uninit_bytes(&mut storage[..]); - fsv.extend(expected.iter().copied()); - assert_eq!(&expected[..], &fsv[..]); - } - - #[test] - fn extend_with_more_than_enough_room() { - let expected = [0u8, 2, 4, 8]; - let mut storage: [MaybeUninit; 100] = unsafe { MaybeUninit::uninit().assume_init() }; - let mut fsv = FixedSliceVec::from_uninit_bytes(&mut storage[..]); - fsv.extend(expected.iter().copied()); - assert_eq!(&expected[..], &fsv[..]); - } - - #[test] - fn extend_with_not_enough_room() { - let expected = [0u8, 2, 4, 8]; - let mut storage: [MaybeUninit; 2] = unsafe { MaybeUninit::uninit().assume_init() }; - let mut fsv = FixedSliceVec::from_uninit_bytes(&mut storage[..]); - fsv.extend(expected.iter().copied()); - assert_eq!(&expected[0..2], &fsv[..]); - } - - #[test] - fn from_uninit_bytes_empty_slice() { - let storage: &mut [MaybeUninit] = &mut []; - let mut fsv: FixedSliceVec = FixedSliceVec::from_uninit_bytes(storage); - assert_eq!(0, fsv.capacity()); - assert_eq!(0, fsv.len()); - assert!(fsv.try_push(31).is_err()); - } - #[test] - fn from_uninit_bytes_smaller_than_item_slice() { - let mut storage: [MaybeUninit; 1] = unsafe { MaybeUninit::uninit().assume_init() }; - let mut fsv: FixedSliceVec = FixedSliceVec::from_uninit_bytes(&mut storage); - assert_eq!(0, fsv.capacity()); - assert_eq!(0, fsv.len()); - assert!(fsv.try_push(31).is_err()); - } - #[test] - fn from_uninit_bytes_larger_than_item_slice() { - let mut storage: [MaybeUninit; 9] = unsafe { MaybeUninit::uninit().assume_init() }; - let mut fsv: FixedSliceVec = FixedSliceVec::from_uninit_bytes(&mut storage); - assert!(fsv.capacity() > 0); - assert_eq!(0, fsv.len()); - assert!(fsv.try_push(31).is_ok()); - assert_eq!(&[31], &fsv[..]); - } - - #[test] - fn equality_sanity_checks() { - let mut storage_a: [MaybeUninit; 2] = unsafe { MaybeUninit::uninit().assume_init() }; - let mut a: FixedSliceVec = FixedSliceVec::from_uninit_bytes(&mut storage_a); - let mut storage_b: [MaybeUninit; 2] = unsafe { MaybeUninit::uninit().assume_init() }; - let mut b: FixedSliceVec = FixedSliceVec::from_uninit_bytes(&mut storage_b); - - assert_eq!(a, b); - assert_eq!(&a, &b[..]); - assert_eq!(&b, &a[..]); - a.push(1u8); - assert_ne!(a, b); - assert_ne!(&a, &b[..]); - assert_ne!(&b, &a[..]); - b.push(1u8); - assert_eq!(a, b); - assert_eq!(&a, &b[..]); - assert_eq!(&b, &a[..]); - } - - #[test] - fn borrow_ish_sanity_checks() { - let mut expected = [0u8, 2, 4, 8]; - let mut storage: [MaybeUninit; 12] = unsafe { MaybeUninit::uninit().assume_init() }; - let mut fsv = FixedSliceVec::from_uninit_bytes(&mut storage[..]); - fsv.extend(expected.iter().copied()); - - assert_eq!(&expected[..], Borrow::<[u8]>::borrow(&fsv)); - assert_eq!(&mut expected[..], BorrowMut::<[u8]>::borrow_mut(&mut fsv)); - assert_eq!(&expected[..], fsv.as_ref()); - assert_eq!(&mut expected[..], fsv.as_mut()); - } - - #[test] - fn comparison_sanity_checks() { - let mut storage_a: [MaybeUninit; 2] = unsafe { MaybeUninit::uninit().assume_init() }; - let mut a: FixedSliceVec = FixedSliceVec::from_uninit_bytes(&mut storage_a); - let mut storage_b: [MaybeUninit; 2] = unsafe { MaybeUninit::uninit().assume_init() }; - let mut b: FixedSliceVec = FixedSliceVec::from_uninit_bytes(&mut storage_b); - use core::cmp::Ordering; - assert_eq!(Some(Ordering::Equal), a.partial_cmp(&b)); - b.push(1); - assert!(a.lt(&b)); - assert!(b.gt(&a)); - a.push(1); - assert!(a.ge(&b)); - assert!(a.le(&b)); - a[0] = 2; - assert!(a.gt(&b)); - assert!(b.lt(&a)); - } - - #[test] - fn insertion_sanity_checks() { - // Opposite order element addition check - let mut storage_a: [MaybeUninit; 4] = unsafe { MaybeUninit::uninit().assume_init() }; - let mut a: FixedSliceVec = FixedSliceVec::from_uninit_bytes(&mut storage_a); - let mut storage_b: [MaybeUninit; 4] = unsafe { MaybeUninit::uninit().assume_init() }; - let mut b: FixedSliceVec = FixedSliceVec::from_uninit_bytes(&mut storage_b); - assert_eq!(a.as_slice(), b.as_slice(), "Equal sets of 0 elements"); - a.insert(0usize, 1); - a.insert(0usize, 2); - a.insert(0usize, 3); - a.insert(0usize, 4); - b.push(4); - b.push(3); - b.push(2); - b.push(1); - assert_eq!( - a.as_slice(), - b.as_slice(), - "Equal sets of 4 elements, added in opposite order" - ); - assert_eq!(a.try_insert(0, 0), b.try_push(0)); - assert_eq!( - a.as_slice(), - &[4u8, 3u8, 2u8, 1u8], - "Insert should not modify until required capacity is verified." - ); - assert_eq!( - b.as_slice(), - &[4u8, 3u8, 2u8, 1u8], - "Push should not modify until required capacity is verified." - ); - - // Mixed insertion order check - a.clear(); - assert!( - a.try_insert(1usize, 55).is_err(), - "Given `n` elements and index `0` to `n-1` the maximum insert index is `n` (push)" - ); - a.insert(0usize, 4); - a.insert(1usize, 2); - a.insert(1usize, 3); - a.insert(3usize, 1); - assert_eq!( - a.as_slice(), - &[4u8, 3u8, 2u8, 1u8], - "Alternate insert order should yeild same result." - ); - - // Zero sized buffer check - let mut storage_c: [MaybeUninit; 0] = unsafe { MaybeUninit::uninit().assume_init() }; - let mut c: FixedSliceVec = FixedSliceVec::from_uninit_bytes(&mut storage_c); - assert!( - c.try_insert(0usize, 1).is_err(), - "Zero sized buffer should fail on insert" - ); - - // Zero remaining capacity check - let mut storage_d: [MaybeUninit; 1] = unsafe { MaybeUninit::uninit().assume_init() }; - let mut d: FixedSliceVec = FixedSliceVec::from_uninit_bytes(&mut storage_d); - d.push(1); - assert!( - d.try_insert(0usize, 2).is_err(), - "Zero remaining capacity should fail an insert" - ); - } -} diff --git a/tests/std_required_tests.rs b/tests/std_required_tests.rs index 4d68545..851b174 100644 --- a/tests/std_required_tests.rs +++ b/tests/std_required_tests.rs @@ -90,49 +90,6 @@ impl Drop for TrueOnDrop { } } -#[test] -fn embed_does_not_run_drops_atomic() { - let flip = Arc::new(AtomicBool::new(false)); - let flip_clone = flip.clone(); - let mut storage: [u8; 16] = [0u8; 16]; - let emb = unsafe { - fixed_slice_vec::single::embed(&mut storage[..], move |_leftovers| { - Result::::Ok(TrueOnDrop(flip_clone)) - }) - .unwrap() - }; - - assert!(!flip.load(Ordering::SeqCst)); - assert!(!emb.0.load(Ordering::SeqCst)); - - // Manually clean up the Arc to avoid alloc-related-leak - unsafe { - let ptr: *const TrueOnDrop = emb; - let extracted = ptr.read(); - std::mem::drop(extracted); - } -} -#[test] -fn embed_uninit_does_not_run_drops_atomic() { - let flip = Arc::new(AtomicBool::new(false)); - let flip_clone = flip.clone(); - let mut storage: [MaybeUninit; 16] = unsafe { MaybeUninit::uninit().assume_init() }; - let emb = fixed_slice_vec::single::embed_uninit(&mut storage[..], move |_leftovers| { - Result::::Ok(TrueOnDrop(flip_clone)) - }) - .unwrap(); - - assert!(!flip.load(Ordering::SeqCst)); - assert!(!emb.0.load(Ordering::SeqCst)); - - // Manually clean up the Arc to avoid alloc-related-leak - unsafe { - let ptr: *const TrueOnDrop = emb; - let extracted = ptr.read(); - std::mem::drop(extracted); - } -} - #[test] fn fsv_does_not_run_drops_on_push_atomic() { let flip = Arc::new(AtomicBool::new(false)); From 6784789b2773854cd287c99f58f2a4e2ceb2093a Mon Sep 17 00:00:00 2001 From: Tage Johansson Date: Wed, 17 Dec 2025 14:16:02 +0100 Subject: [PATCH 2/9] fix clippy warning --- src/lib.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c90af9f..0d7145b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -314,9 +314,7 @@ impl<'a, T: Sized> FixedSliceVec<'a, T> { // on the `len` field, which we have updated above already. // The early setting of `len` is designed to avoid double-free errors if there // is a panic in the middle of the following drop. - (core::slice::from_raw_parts_mut(self.storage.as_mut_ptr() as *mut T, original_len) - as *mut [T]) - .drop_in_place(); + core::ptr::slice_from_raw_parts_mut(self.storage.as_mut_ptr() as *mut T, original_len).drop_in_place(); } } From 2f00b80ad2e4b7d92a068734c537445a26eda040 Mon Sep 17 00:00:00 2001 From: Tage Johansson Date: Wed, 17 Dec 2025 14:47:30 +0100 Subject: [PATCH 3/9] Update authors list --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 7355114..be0b23f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ authors = [ "Jon Lamb ", "Russell Mull ", "dan pittman ", + "Tage Johansson ", ] edition = "2018" license = "Apache-2.0" From 8a2762dc7f006daeefdc68bcca2cff9052a66512 Mon Sep 17 00:00:00 2001 From: Tage Johansson Date: Tue, 30 Dec 2025 11:26:13 +0100 Subject: [PATCH 4/9] Add `into_storage()` and `into_byte_storage()` methods --- src/lib.rs | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 0d7145b..7bbb58d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -195,6 +195,32 @@ impl<'a, T: Sized> FixedSliceVec<'a, T> { self.storage.as_ptr() } + /// Convert the FixedSliceVec into the underlying storage. Drop will be called for all items in + /// the vec. + pub fn into_storage(self) -> &'a mut [MaybeUninit] { + let storage_ptr = &raw const self.storage; + drop(self); + unsafe { core::ptr::read(storage_ptr) } + } + + /// Convert the FixedSliceVec into the underlying storage as bytes. Drop will be called for all + /// items in the vec. + /// + /// The length of the returned slice will be the size of `T` times the capacity. It might be + /// slightly shorter than the slice passed to for instance + /// [`FixedSliceVec::from_uninit_bytes()`] due to alignment. + pub fn into_byte_storage(self) -> &'a mut [MaybeUninit] { + let storage_ptr = &raw const self.storage; + drop(self); + let storage = unsafe { core::ptr::read(storage_ptr) }; + unsafe { + core::slice::from_raw_parts_mut( + storage.as_mut_ptr().cast(), + storage.len() * size_of::(), + ) + } + } + /// The length of the FixedSliceVec. The number of initialized /// values that have been added to it. #[inline] @@ -976,4 +1002,39 @@ mod tests { "Zero remaining capacity should fail an insert" ); } + + #[test] + fn into_storage() { + let mut storage = [MaybeUninit::::uninit(); 8]; + + assert_eq!(8, FixedSliceVec::new(&mut storage).into_storage().len()); + assert_eq!( + 64, + FixedSliceVec::new(&mut storage).into_byte_storage().len() + ); + + // Make sure that drop is called. + let foo = std::rc::Rc::new(42); + let mut storage = [MaybeUninit::::uninit(); 256]; + + let mut v = FixedSliceVec::from_uninit_bytes(&mut storage); + v.push(foo.clone()); + v.push(foo.clone()); + assert_eq!(3, std::rc::Rc::strong_count(&foo)); + + let v_cap = v.capacity(); + let storage_2 = v.into_byte_storage(); + assert_eq!(1, std::rc::Rc::strong_count(&foo)); + assert_eq!(v_cap * size_of_val(&foo), storage_2.len()); + + let mut v = FixedSliceVec::from_uninit_bytes(storage_2); + v.push(foo.clone()); + v.push(foo.clone()); + assert_eq!(3, std::rc::Rc::strong_count(&foo)); + + let v_cap = v.capacity(); + let storage_3 = v.into_storage(); + assert_eq!(1, std::rc::Rc::strong_count(&foo)); + assert_eq!(v_cap, storage_3.len()); + } } From 86eaacfdff38be3908b0fd82dcdf2cae1cda994b Mon Sep 17 00:00:00 2001 From: Tage Johansson Date: Tue, 30 Dec 2025 16:21:47 +0100 Subject: [PATCH 5/9] Implement remove_range() --- src/lib.rs | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 7bbb58d..8bd0c76 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ use core::convert::From; use core::hash::{Hash, Hasher}; use core::iter::Extend; use core::mem::MaybeUninit; +use core::ops::{Bound, RangeBounds}; use core::ops::{Deref, DerefMut}; /// Vec-like structure backed by a storage slice of possibly uninitialized data. @@ -363,6 +364,54 @@ impl<'a, T: Sized> FixedSliceVec<'a, T> { (&mut core::slice::from_raw_parts_mut(self.storage.as_mut_ptr() as *mut T, original_len)[len..] as *mut [T]).drop_in_place(); } } + + /// Remove a range of elements from the FixedSliceVec. + /// + /// This is similar to the `drain()` method on `Vec` except that it just drops the removed + /// elements instead of returning an iterator to them. The implementation is much simpler in + /// this case. + /// + /// Panics if `start > end` or `end` is passed the end of the vec. + pub fn remove_range(&mut self, range: impl RangeBounds) { + // XXX: Use slice::range() to compute start and end once it is stabilised. + let end = match range.end_bound() { + Bound::Included(&end) => end + 1, + Bound::Excluded(&end) => end, + Bound::Unbounded => self.len(), + }; + assert!(end <= self.len(), "range end too large"); + + let start = match range.start_bound() { + Bound::Excluded(&start) => start + 1, + Bound::Included(&start) => start, + Bound::Unbounded => 0, + }; + if start == end { + return; + } + assert!(start < end, "start <= end"); + let len = self.len(); + + // Drop the items in start..end. + // We temporarily set the length to start to avoid UB in case of a panic. + self.len = start; + for item in &mut self.storage[start..end] { + unsafe { item.assume_init_drop() } + } + + // Move the tail to begin at start. + let tail_len = len - end; + if tail_len > 0 { + unsafe { + let dst_ptr = self.as_mut_ptr().add(start); + let src_ptr = self.as_ptr().add(end); + core::ptr::copy(src_ptr, dst_ptr, tail_len); + } + } + + self.len = start + tail_len; + } + /// Removes and returns the element at position `index` within the FixedSliceVec, /// shifting all elements after it to the left. /// @@ -1037,4 +1086,47 @@ mod tests { assert_eq!(1, std::rc::Rc::strong_count(&foo)); assert_eq!(v_cap, storage_3.len()); } + + #[test] + fn remove_range() { + let mut storage = [MaybeUninit::::uninit(); 16]; + let mut vec = FixedSliceVec::new(&mut storage); + + vec.remove_range(..); + assert!(vec.is_empty()); + + vec.extend(0..16); + vec.remove_range(12..); + assert!(vec.iter().copied().eq(0..12)); + + vec.remove_range(11..=11); + assert!(vec.iter().copied().eq(0..11)); + vec.remove_range(1..1); + assert!(vec.iter().copied().eq(0..11)); + + vec.remove_range(2..9); + assert_eq!([0, 1, 9, 10], vec.as_slice()); + vec.remove_range(..0); + assert_eq!([0, 1, 9, 10], vec.as_slice()); + vec.remove_range(..2); + assert_eq!([9, 10], vec.as_slice()); + vec.remove_range(..); + assert!(vec.is_empty()); + + // Check that drop is called properly. + let mut storage = [MaybeUninit::::uninit(); 1024]; + let mut vec = FixedSliceVec::from_uninit_bytes(&mut storage); + let rc = std::rc::Rc::new(42); + for _ in 0..16 { + vec.push(rc.clone()); + } + assert_eq!(17, std::rc::Rc::strong_count(&rc)); + + vec.remove_range(2..10); + assert_eq!(9, std::rc::Rc::strong_count(&rc)); + vec.remove_range(4..); + assert_eq!(5, std::rc::Rc::strong_count(&rc)); + vec.remove_range(..); + assert_eq!(1, std::rc::Rc::strong_count(&rc)); + } } From 3b3acc28a87271d0a934c92424197bbd03e1d4a6 Mon Sep 17 00:00:00 2001 From: Tage Johansson Date: Tue, 30 Dec 2025 16:22:11 +0100 Subject: [PATCH 6/9] fix fmt --- src/lib.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 8bd0c76..331ad05 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -312,7 +312,9 @@ impl<'a, T: Sized> FixedSliceVec<'a, T> { self.storage[self.len] = MaybeUninit::new(item); self.len += 1; } else { - unreachable!("`FixedSliceVec::try_extend` peeked above to ensure that `next` would return Some") + unreachable!( + "`FixedSliceVec::try_extend` peeked above to ensure that `next` would return Some" + ) } } else { return Ok(()); @@ -341,7 +343,8 @@ impl<'a, T: Sized> FixedSliceVec<'a, T> { // on the `len` field, which we have updated above already. // The early setting of `len` is designed to avoid double-free errors if there // is a panic in the middle of the following drop. - core::ptr::slice_from_raw_parts_mut(self.storage.as_mut_ptr() as *mut T, original_len).drop_in_place(); + core::ptr::slice_from_raw_parts_mut(self.storage.as_mut_ptr() as *mut T, original_len) + .drop_in_place(); } } From 5324bbaf0aeb7386365e7855c47d4636a5ab4850 Mon Sep 17 00:00:00 2001 From: Tage Johansson Date: Thu, 8 Jan 2026 15:04:09 +0100 Subject: [PATCH 7/9] implement into_slice() --- src/lib.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 331ad05..240a735 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -196,6 +196,15 @@ impl<'a, T: Sized> FixedSliceVec<'a, T> { self.storage.as_ptr() } + /// Consume this [`FixedSliceVec`] and get a slice with all initialised items. (Works only for + /// [`Copy`] types as they don't require drop.) + pub fn into_slice(self) -> &'a mut [T] + where + T: Copy, + { + unsafe { core::slice::from_raw_parts_mut(self.storage.as_mut_ptr() as *mut T, self.len) } + } + /// Convert the FixedSliceVec into the underlying storage. Drop will be called for all items in /// the vec. pub fn into_storage(self) -> &'a mut [MaybeUninit] { From 001173616a11227d1871fdaaaf5c7d6aa88e24cc Mon Sep 17 00:00:00 2001 From: Tage Johansson Date: Fri, 9 Jan 2026 09:42:28 +0100 Subject: [PATCH 8/9] mark the crate no_std --- src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 240a735..f14154a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ //! FixedSliceVec is a structure for defining variably populated vectors backed //! by a slice of storage capacity. +#![no_std] use core::borrow::{Borrow, BorrowMut}; use core::convert::From; use core::hash::{Hash, Hasher}; @@ -678,6 +679,8 @@ impl<'a, T: Sized> Extend for FixedSliceVec<'a, T> { #[cfg(test)] mod tests { + extern crate std; + use super::*; #[test] From 35119c53c477765a4e1b7e12610eef0e0a985eda Mon Sep 17 00:00:00 2001 From: Tage Johansson Date: Mon, 19 Jan 2026 10:11:02 +0100 Subject: [PATCH 9/9] add extend_from_slice() --- src/lib.rs | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index f14154a..b971a9c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -332,6 +332,38 @@ impl<'a, T: Sized> FixedSliceVec<'a, T> { } } + /// Attempt to extend by a slice by copying all values. + pub fn try_extend_from_slice(&mut self, s: &[T]) -> Result<(), StorageError<()>> + where + T: Copy, + { + if s.is_empty() { + return Ok(()); + } + if s.len() > self.storage.len() - self.len { + return Err(StorageError(())); + } + unsafe { + self.storage[self.len..] + .as_mut_ptr() + .copy_from_nonoverlapping(s.as_ptr() as *const MaybeUninit, s.len()); + } + self.len += s.len(); + Ok(()) + } + + /// Extend from a slice by copying all values. + /// + /// # Panics + /// + /// Panics if `self.len() + s.len() > self.capacity()`. + pub fn extend_from_slice(&mut self, s: &[T]) + where + T: Copy, + { + self.try_extend_from_slice(s).unwrap(); + } + /// Remove the last item from the FixedSliceVec. #[inline] pub fn pop(&mut self) -> Option { @@ -894,6 +926,9 @@ mod tests { assert_eq!(Some(8), out_iter.next()); assert_eq!(None, out_iter.next()); assert_eq!(&expected[0..2], &fsv[..]); + fsv.clear(); + assert!(fsv.try_extend_from_slice(&expected).is_err()); + assert!(fsv.is_empty()); } #[test] fn extend_with_exactly_enough_room() { @@ -902,6 +937,9 @@ mod tests { let mut fsv = FixedSliceVec::from_uninit_bytes(&mut storage[..]); fsv.extend(expected.iter().copied()); assert_eq!(&expected[..], &fsv[..]); + fsv.clear(); + fsv.extend_from_slice(&expected); + assert_eq!(&expected[..], &fsv[..]); } #[test] @@ -911,6 +949,9 @@ mod tests { let mut fsv = FixedSliceVec::from_uninit_bytes(&mut storage[..]); fsv.extend(expected.iter().copied()); assert_eq!(&expected[..], &fsv[..]); + fsv.clear(); + fsv.extend_from_slice(&expected); + assert_eq!(&expected[..], &fsv[..]); } #[test]