diff --git a/src/lib.rs b/src/lib.rs index ff75aff..a57d429 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,7 +42,7 @@ mod internal { macro_rules! define_variants { ( $(#[$meta:meta])* - fn $name:ident($self:ident : $self_ty:ty $(, $param:ident: $param_ty:ty)*) $(-> $ret:ty)?, + fn $name:ident($self:ident: $self_ty:ty $(, $param:ident: $param_ty:ty)*) $(-> $ret:ty)?, $(where_clause: { $($where_clause:tt)* } )? normal_brief: $normal_brief:literal, @@ -109,7 +109,7 @@ mod internal { /// #[doc = concat!(" See also [`Self::", stringify!($name), "`] for the panic-on-error version and [`Self::", stringify!($unchecked_fn), "`] for the unchecked version.")] /// - /// Returns `None` if any of these conditions are **`false`**: + /// Returns [`None`] if any of these conditions are **`false`**: $( #[doc = concat!(" - `", stringify!($ub_condition), "`")] )+ $( /// diff --git a/src/stack_string.rs b/src/stack_string.rs index d9999c4..aa7c082 100644 --- a/src/stack_string.rs +++ b/src/stack_string.rs @@ -48,6 +48,123 @@ impl StackString { empty_collection!() } + /// Clears the string by setting `len = 0`. + /// + /// The underlying memory is not zeroed. + /// + /// # Examples + /// + /// ``` + /// use stack_collections::StackString; + /// + /// let mut stack_string = StackString::<8>::new(); + /// stack_string.push_str("hello"); + /// assert!(!stack_string.is_empty()); + /// + /// stack_string.clear(); + /// assert!(stack_string.is_empty()); + /// assert_eq!(stack_string.len(), 0); + /// assert_eq!(stack_string.as_str(), ""); + /// ``` + #[inline] + pub const fn clear(&mut self) { + self.len = 0; + } + + /// Returns the current length in bytes. + /// + /// # Examples + /// + /// ``` + /// use stack_collections::StackString; + /// + /// let mut stack_string = StackString::<16>::new(); + /// assert_eq!(stack_string.len(), 0); + /// stack_string.push_str("hello"); + /// assert_eq!(stack_string.len(), 5); + /// ``` + #[must_use] + #[inline] + pub const fn len(&self) -> usize { + self.len + } + + /// Returns the capacity in bytes. + /// + /// # Examples + /// + /// ``` + /// use stack_collections::StackString; + /// + /// let stack_string = StackString::<32>::new(); + /// assert_eq!(stack_string.capacity(), 32); + /// ``` + #[expect(clippy::inline_always, reason = "this method is trivial")] + #[inline(always)] + #[must_use] + pub const fn capacity(&self) -> usize { + N + } + + /// Returns the remaining capacity in bytes. + /// + /// # Examples + /// + /// ``` + /// use stack_collections::StackString; + /// + /// let mut stack_string = StackString::<10>::new(); + /// assert_eq!(stack_string.remaining_capacity(), 10); + /// + /// stack_string.push_str("hello"); + /// assert_eq!(stack_string.remaining_capacity(), 5); + /// + /// stack_string.push_str("world"); + /// assert_eq!(stack_string.remaining_capacity(), 0); + /// assert!(stack_string.is_full()); + /// ``` + #[must_use] + #[inline] + pub const fn remaining_capacity(&self) -> usize { + N - self.len + } + + /// Returns `true` if the string is empty. + /// + /// # Examples + /// + /// ``` + /// use stack_collections::StackString; + /// + /// let mut stack_string = StackString::<8>::new(); + /// assert!(stack_string.is_empty()); + /// stack_string.push('a'); + /// assert!(!stack_string.is_empty()); + /// ``` + #[must_use] + #[inline] + pub const fn is_empty(&self) -> bool { + self.len == 0 + } + + /// Returns `true` if the string is at full capacity. + /// + /// # Examples + /// + /// ``` + /// use stack_collections::StackString; + /// + /// let mut stack_string = StackString::<5>::new(); + /// assert!(!stack_string.is_full()); + /// stack_string.push_str("hello"); + /// assert!(stack_string.is_full()); + /// ``` + #[must_use] + #[inline] + pub const fn is_full(&self) -> bool { + self.len >= N + } + /// Returns a raw pointer to the string's buffer. /// /// # Examples @@ -184,9 +301,196 @@ impl StackString { self.len = new_len; } + /// Returns the contents as a slice of initialized bytes. + /// + /// # Examples + /// + /// ``` + /// use stack_collections::StackString; + /// + /// let mut stack_string = StackString::<8>::new(); + /// stack_string.push_str("hello"); + /// assert_eq!(stack_string.as_bytes(), b"hello"); + /// ``` + #[must_use] + #[inline] + pub const fn as_bytes(&self) -> &[u8] { + // SAFETY: self.len bytes starting from buf are initialized valid UTF-8 + unsafe { slice::from_raw_parts(self.as_ptr(), self.len) } + } + + /// Borrow the content as `str`. + /// + /// # Examples + /// + /// ``` + /// use stack_collections::StackString; + /// + /// let mut stack_string = StackString::<8>::new(); + /// stack_string.push_str("test"); + /// assert_eq!(stack_string.as_str(), "test"); + /// ``` + #[must_use] + #[inline] + pub const fn as_str(&self) -> &str { + // SAFETY: self.as_bytes() returns valid UTF-8 by construction + unsafe { str::from_utf8_unchecked(self.as_bytes()) } + } + /// Borrow the content as a mutable `str`. + /// + /// # Examples + /// + /// ``` + /// use stack_collections::StackString; + /// + /// let mut stack_string = StackString::<16>::new(); + /// stack_string.push_str("hello"); + /// + /// let mutable_str = stack_string.as_mut_str(); + /// mutable_str.make_ascii_uppercase(); + /// + /// assert_eq!(stack_string.as_str(), "HELLO"); + /// ``` + #[must_use] + #[inline] + pub const fn as_mut_str(&mut self) -> &mut str { + // SAFETY: Creating mutable slice of initialized valid UTF-8 bytes + let slice = unsafe { slice::from_raw_parts_mut(self.as_mut_ptr(), self.len) }; + // SAFETY: slice contains valid UTF-8 by construction + unsafe { str::from_utf8_unchecked_mut(slice) } + } + + /// Truncates the string to the specified byte length. + /// + /// # Panics + /// + /// A panic if `new_len` does not lie on a UTF-8 character boundary. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use stack_collections::StackString; + /// + /// let mut stack_string = StackString::<16>::new(); + /// stack_string.push_str("hello"); + /// stack_string.truncate(3); + /// assert_eq!(stack_string.as_str(), "hel"); + /// assert_eq!(stack_string.len(), 3); + /// + /// stack_string.truncate(10); + /// assert_eq!(stack_string.as_str(), "hel"); + /// ``` + /// + /// A panic upon invalid UTF-8 character boundary: + /// + /// ```should_panic + /// use stack_collections::StackString; + /// + /// let mut stack_string = StackString::<16>::new(); + /// stack_string.push_str("hello😀"); + /// + /// // this will panic at runtime + /// stack_string.truncate(6); + /// ``` + #[inline] + pub const fn truncate(&mut self, new_len: usize) { + if new_len >= self.len { + return; + } + assert!(self.as_str().is_char_boundary(new_len), "truncate not on char boundary"); + self.len = new_len; + } + + /// Writes `value` to `dst`. + #[expect( + clippy::cast_possible_truncation, + reason = "Casting from u32 to u8 is safe here because UTF-8 encoding ensures all written byte values are <= 0xFF" + )] + #[inline(always)] + const unsafe fn write_char_to_ptr(dst: *mut u8, value: char, value_len: usize) { + const TAG_CONT: u8 = 0b1000_0000; + const TAG_TWO_B: u8 = 0b1100_0000; + const TAG_THREE_B: u8 = 0b1110_0000; + const TAG_FOUR_B: u8 = 0b1111_0000; + + const CONT_MASK: u32 = 0x3F; + let code = value as u32; + + match value_len { + 1 => { + // SAFETY: Caller ensures dst has space for 1 byte + unsafe { + dst.write(code as u8); + } + } + 2 => { + // SAFETY: Caller ensures dst has space for byte 0 + unsafe { + dst.write(TAG_TWO_B | ((code >> 6_u32) as u8)); + } + // SAFETY: Caller ensures dst has space for byte 1 + let dst1 = unsafe { dst.add(1) }; + // SAFETY: Writing to valid pointer dst1 + unsafe { + dst1.write(TAG_CONT | ((code & CONT_MASK) as u8)); + } + } + 3 => { + // SAFETY: Caller ensures dst has space for byte 0 + unsafe { + dst.write(TAG_THREE_B | ((code >> 12_u32) as u8)); + } + // SAFETY: Caller ensures dst has space for byte 1 + let dst1 = unsafe { dst.add(1) }; + // SAFETY: Writing to valid pointer dst1 + unsafe { + dst1.write(TAG_CONT | (((code >> 6_u32) & CONT_MASK) as u8)); + } + // SAFETY: Caller ensures dst has space for byte 2 + let dst2 = unsafe { dst.add(2) }; + // SAFETY: Writing to valid pointer dst2 + unsafe { + dst2.write(TAG_CONT | ((code & CONT_MASK) as u8)); + } + } + 4 => { + // SAFETY: Caller ensures dst has space for byte 0 + unsafe { + dst.write(TAG_FOUR_B | ((code >> 18_u32) as u8)); + } + // SAFETY: Caller ensures dst has space for byte 1 + let dst1 = unsafe { dst.add(1) }; + // SAFETY: Writing to valid pointer dst1 + unsafe { + dst1.write(TAG_CONT | (((code >> 12_u32) & CONT_MASK) as u8)); + } + // SAFETY: Caller ensures dst has space for byte 2 + let dst2 = unsafe { dst.add(2) }; + // SAFETY: Writing to valid pointer dst2 + unsafe { + dst2.write(TAG_CONT | (((code >> 6_u32) & CONT_MASK) as u8)); + } + // SAFETY: Caller ensures dst has space for byte 3 + let dst3 = unsafe { dst.add(3) }; + // SAFETY: Writing to valid pointer dst3 + unsafe { + dst3.write(TAG_CONT | ((code & CONT_MASK) as u8)); + } + } + _ => { + // SAFETY: the caller promises that value_len satisfies one of the above matches + // for valid char encoding + unsafe { unreachable_unchecked() } + } + } + } + + // pop define_variants! { - fn pop(self : &mut Self) -> char, + fn pop(self: &mut Self) -> char, normal_brief: "Removes and returns the last char", try_brief: "Attempts to remove and return the last char", @@ -295,122 +599,43 @@ impl StackString { /// /// let mut stack_string = StackString::<16>::new(); /// stack_string.push_str("hello😀world"); - /// - /// assert_eq!(stack_string.pop(), 'd'); - /// assert_eq!(stack_string.pop(), 'l'); - /// assert_eq!(stack_string.pop(), 'r'); - /// assert_eq!(stack_string.pop(), 'o'); - /// assert_eq!(stack_string.pop(), 'w'); - /// assert_eq!(stack_string.pop(), '😀'); - /// assert_eq!(stack_string.as_str(), "hello"); - /// ``` - /// - /// A panic if the string is empty: - /// - ///```should_panic - /// use stack_collections::StackString; - /// - /// let mut stack_string = StackString::<8>::new(); - /// - /// // this will panic at runtime - /// stack_string.pop(); - /// ``` - } - try: { - /// ``` - /// use stack_collections::StackString; - /// - /// let mut stack_string = StackString::<8>::new(); - /// assert!(stack_string.try_pop().is_none()); - /// - /// stack_string.push_str("hi"); - /// assert_eq!(stack_string.try_pop(), Some('i')); - /// assert_eq!(stack_string.try_pop(), Some('h')); - /// assert_eq!(stack_string.try_pop(), None); - /// ``` - } - } - } - - /// Writes `value` to `dst` - #[expect( - clippy::cast_possible_truncation, - reason = "Casting from u32 to u8 is safe here because UTF-8 encoding ensures all written byte values are <= 0xFF" - )] - #[inline(always)] - const unsafe fn write_char_to_ptr(dst: *mut u8, value: char, value_len: usize) { - const TAG_CONT: u8 = 0b1000_0000; - const TAG_TWO_B: u8 = 0b1100_0000; - const TAG_THREE_B: u8 = 0b1110_0000; - const TAG_FOUR_B: u8 = 0b1111_0000; - - const CONT_MASK: u32 = 0x3F; - - let code = value as u32; - - match value_len { - 1 => { - // SAFETY: Caller ensures dst has space for 1 byte - unsafe { - dst.write(code as u8); - } - } - 2 => { - // SAFETY: Caller ensures dst has space for byte 0 - unsafe { - dst.write(TAG_TWO_B | ((code >> 6_u32) as u8)); - } - // SAFETY: Caller ensures dst has space for byte 1 - let dst1 = unsafe { dst.add(1) }; - // SAFETY: Writing to valid pointer dst1 - unsafe { - dst1.write(TAG_CONT | ((code & CONT_MASK) as u8)); - } - } - 3 => { - // SAFETY: Caller ensures dst has space for byte 0 - unsafe { - dst.write(TAG_THREE_B | ((code >> 12_u32) as u8)); - } - // SAFETY: Caller ensures dst has space for byte 1 - let dst1 = unsafe { dst.add(1) }; - // SAFETY: Writing to valid pointer dst1 - unsafe { - dst1.write(TAG_CONT | (((code >> 6_u32) & CONT_MASK) as u8)); - } - // SAFETY: Caller ensures dst has space for byte 2 - let dst2 = unsafe { dst.add(2) }; - // SAFETY: Writing to valid pointer dst2 - unsafe { - dst2.write(TAG_CONT | ((code & CONT_MASK) as u8)); - } - } - _ => { - // SAFETY: Caller ensures dst has space for byte 0 - unsafe { - dst.write(TAG_FOUR_B | ((code >> 18_u32) as u8)); - } - // SAFETY: Caller ensures dst has space for byte 1 - let dst1 = unsafe { dst.add(1) }; - // SAFETY: Writing to valid pointer dst1 - unsafe { - dst1.write(TAG_CONT | (((code >> 12_u32) & CONT_MASK) as u8)); - } - // SAFETY: Caller ensures dst has space for byte 2 - let dst2 = unsafe { dst.add(2) }; - // SAFETY: Writing to valid pointer dst2 - unsafe { - dst2.write(TAG_CONT | (((code >> 6_u32) & CONT_MASK) as u8)); - } - // SAFETY: Caller ensures dst has space for byte 3 - let dst3 = unsafe { dst.add(3) }; - // SAFETY: Writing to valid pointer dst3 - unsafe { - dst3.write(TAG_CONT | ((code & CONT_MASK) as u8)); - } + /// + /// assert_eq!(stack_string.pop(), 'd'); + /// assert_eq!(stack_string.pop(), 'l'); + /// assert_eq!(stack_string.pop(), 'r'); + /// assert_eq!(stack_string.pop(), 'o'); + /// assert_eq!(stack_string.pop(), 'w'); + /// assert_eq!(stack_string.pop(), '😀'); + /// assert_eq!(stack_string.as_str(), "hello"); + /// ``` + /// + /// A panic if the string is empty: + /// + ///```should_panic + /// use stack_collections::StackString; + /// + /// let mut stack_string = StackString::<8>::new(); + /// + /// // this will panic at runtime + /// stack_string.pop(); + /// ``` + } + try: { + /// ``` + /// use stack_collections::StackString; + /// + /// let mut stack_string = StackString::<8>::new(); + /// assert!(stack_string.try_pop().is_none()); + /// + /// stack_string.push_str("hi"); + /// assert_eq!(stack_string.try_pop(), Some('i')); + /// assert_eq!(stack_string.try_pop(), Some('h')); + /// assert_eq!(stack_string.try_pop(), None); + /// ``` } } } + // push define_variants! { fn push(self: &mut Self, value: char) -> (), @@ -566,15 +791,14 @@ impl StackString { } // push_str - #[rustfmt::skip] - define_variants!( + define_variants! { fn push_str(self: &mut Self, string: &str) -> (), normal_brief: "Appends a `str`", try_brief: "Attempts to append a `str`", unchecked_brief_suffix: "without bound checking", ub_conditions: { - self.len + string.len() > N => "buffer capacity exceeded", + self.len() + string.len() > N => "buffer capacity exceeded", }, prefixes: { normal: {pub const}, @@ -641,7 +865,7 @@ impl StackString { /// ``` } } - ); + } // insert define_variants! { @@ -651,8 +875,8 @@ impl StackString { try_brief: "Attempts to insert a `char` at `index`, shifting all elements after it", unchecked_brief_suffix: "without bound or capacity checking", ub_conditions: { - index > self.len => "index out of bounds", - self.len + value.len_utf8() > N => "buffer capacity exceeded", + index > self.len() => "index out of bounds", + self.len() + value.len_utf8() > N => "buffer capacity exceeded", }, prefixes: { normal: {pub const}, @@ -759,7 +983,7 @@ impl StackString { try_brief: "Attempts to insert an ASCII character at `index`, shifting all elements after it", unchecked_brief_suffix: "without bound or capacity checking", ub_conditions: { - index > self.len => "index out of bounds", + index > self.len() => "index out of bounds", self.is_full() => "buffer capacity exceeded", }, prefixes: { @@ -874,8 +1098,8 @@ impl StackString { try_brief: "Attempts to insert a `str` at `index`, shifting all elements after it", unchecked_brief_suffix: "without bound or capacity checking", ub_conditions: { - index > self.len => "index out of bounds", - self.len + string.len() > N => "buffer capacity exceeded", + index > self.len() => "index out of bounds", + self.len() + string.len() > N => "buffer capacity exceeded", }, prefixes: { normal: {pub const}, @@ -970,225 +1194,6 @@ impl StackString { } } } - - /// Truncates the string to the specified byte length. - /// - /// # Panics - /// - /// A panic if `new_len` does not lie on a UTF-8 character boundary. - /// - /// # Examples - /// - /// Basic usage: - /// - /// ``` - /// use stack_collections::StackString; - /// - /// let mut stack_string = StackString::<16>::new(); - /// stack_string.push_str("hello"); - /// stack_string.truncate(3); - /// assert_eq!(stack_string.as_str(), "hel"); - /// assert_eq!(stack_string.len(), 3); - /// - /// stack_string.truncate(10); - /// assert_eq!(stack_string.as_str(), "hel"); - /// ``` - /// - /// A panic upon invalid UTF-8 character boundary: - /// - /// ```should_panic - /// use stack_collections::StackString; - /// - /// let mut stack_string = StackString::<16>::new(); - /// stack_string.push_str("hello😀"); - /// - /// // this will panic at runtime - /// stack_string.truncate(6); - /// ``` - #[inline] - pub const fn truncate(&mut self, new_len: usize) { - if new_len >= self.len { - return; - } - assert!(self.as_str().is_char_boundary(new_len), "truncate not on char boundary"); - self.len = new_len; - } - - /// Returns the contents as a slice of initialized bytes. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackString; - /// - /// let mut stack_string = StackString::<8>::new(); - /// stack_string.push_str("hello"); - /// assert_eq!(stack_string.as_bytes(), b"hello"); - /// ``` - #[must_use] - #[inline] - pub const fn as_bytes(&self) -> &[u8] { - // SAFETY: self.len bytes starting from buf are initialized valid UTF-8 - unsafe { slice::from_raw_parts(self.as_ptr(), self.len) } - } - - /// Borrow the content as `str`. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackString; - /// - /// let mut stack_string = StackString::<8>::new(); - /// stack_string.push_str("test"); - /// assert_eq!(stack_string.as_str(), "test"); - /// ``` - #[must_use] - #[inline] - pub const fn as_str(&self) -> &str { - // SAFETY: self.as_bytes() returns valid UTF-8 by construction - unsafe { str::from_utf8_unchecked(self.as_bytes()) } - } - /// Borrow the content as a mutable `str`. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackString; - /// - /// let mut stack_string = StackString::<16>::new(); - /// stack_string.push_str("hello"); - /// - /// let mutable_str = stack_string.as_mut_str(); - /// mutable_str.make_ascii_uppercase(); - /// - /// assert_eq!(stack_string.as_str(), "HELLO"); - /// ``` - #[must_use] - #[inline] - pub const fn as_mut_str(&mut self) -> &mut str { - // SAFETY: Creating mutable slice of initialized valid UTF-8 bytes - let slice = unsafe { slice::from_raw_parts_mut(self.as_mut_ptr(), self.len) }; - // SAFETY: slice contains valid UTF-8 by construction - unsafe { str::from_utf8_unchecked_mut(slice) } - } - - /// Clears the string by setting `len = 0`. - /// - /// The underlying memory is not zeroed. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackString; - /// - /// let mut stack_string = StackString::<8>::new(); - /// stack_string.push_str("hello"); - /// assert!(!stack_string.is_empty()); - /// - /// stack_string.clear(); - /// assert!(stack_string.is_empty()); - /// assert_eq!(stack_string.len(), 0); - /// assert_eq!(stack_string.as_str(), ""); - /// ``` - #[inline] - pub const fn clear(&mut self) { - self.len = 0; - } - - /// Returns the current length in bytes. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackString; - /// - /// let mut stack_string = StackString::<16>::new(); - /// assert_eq!(stack_string.len(), 0); - /// stack_string.push_str("hello"); - /// assert_eq!(stack_string.len(), 5); - /// ``` - #[must_use] - #[inline] - pub const fn len(&self) -> usize { - self.len - } - - /// Returns the capacity in bytes. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackString; - /// - /// let stack_string = StackString::<32>::new(); - /// assert_eq!(stack_string.capacity(), 32); - /// ``` - #[expect(clippy::inline_always, reason = "this method is trivial")] - #[inline(always)] - #[must_use] - pub const fn capacity(&self) -> usize { - N - } - - /// Returns the remaining capacity in bytes. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackString; - /// - /// let mut stack_string = StackString::<10>::new(); - /// assert_eq!(stack_string.remaining_capacity(), 10); - /// - /// stack_string.push_str("hello"); - /// assert_eq!(stack_string.remaining_capacity(), 5); - /// - /// stack_string.push_str("world"); - /// assert_eq!(stack_string.remaining_capacity(), 0); - /// assert!(stack_string.is_full()); - /// ``` - #[must_use] - #[inline] - pub const fn remaining_capacity(&self) -> usize { - N - self.len - } - - /// Returns `true` if the string is empty. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackString; - /// - /// let mut stack_string = StackString::<8>::new(); - /// assert!(stack_string.is_empty()); - /// stack_string.push('a'); - /// assert!(!stack_string.is_empty()); - /// ``` - #[must_use] - #[inline] - pub const fn is_empty(&self) -> bool { - self.len == 0 - } - - /// Returns `true` if the string is at full capacity. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackString; - /// - /// let mut stack_string = StackString::<5>::new(); - /// assert!(!stack_string.is_full()); - /// stack_string.push_str("hello"); - /// assert!(stack_string.is_full()); - /// ``` - #[must_use] - #[inline] - pub const fn is_full(&self) -> bool { - self.len >= N - } } impl Default for StackString { diff --git a/src/stack_vec.rs b/src/stack_vec.rs index f96fa5b..03be4ea 100644 --- a/src/stack_vec.rs +++ b/src/stack_vec.rs @@ -19,6 +19,21 @@ pub struct StackVec { } impl StackVec { + /// Drops all initialized elements in the vector without resetting `self.len`. + #[inline] + fn drop_elements(&mut self, start: usize) { + if size_of::() == 0 || mem::needs_drop::() { + for i in start..self.len { + // SAFETY: i < self.len, so element is initialized + let elem = unsafe { self.buf.get_unchecked_mut(i) }; + // SAFETY: elem points to initialized element + unsafe { + elem.assume_init_drop(); + } + } + } + } + /// Creates a new [`StackVec`]. /// /// # Examples @@ -39,6 +54,127 @@ impl StackVec { empty_collection!() } + /// Clears the vector, dropping all initialized elements if necessary, + /// and resets the length to zero. + /// + /// For types `T` that implement [`Copy`] or do not require [`Drop`], this is + /// effectively just setting `len = 0` without running any destructors. + /// + /// # Examples + /// + /// ``` + /// use stack_collections::StackVec; + /// + /// let mut vec = StackVec::::new(); + /// vec.push(1); + /// vec.push(2); + /// + /// vec.clear(); + /// assert_eq!(vec.len(), 0); + /// assert!(vec.is_empty()); + /// ``` + #[inline] + pub fn clear(&mut self) { + self.drop_elements(0); + self.len = 0; + } + + /// Returns the current length. + /// + /// # Examples + /// + /// ``` + /// use stack_collections::StackVec; + /// + /// let mut vec = StackVec::::new(); + /// assert_eq!(vec.len(), 0); + /// vec.push(1); + /// assert_eq!(vec.len(), 1); + /// ``` + #[inline] + #[must_use] + pub const fn len(&self) -> usize { + self.len + } + + /// Returns the capacity. + /// + /// # Examples + /// + /// ``` + /// use stack_collections::StackVec; + /// + /// let v = StackVec::::new(); + /// assert_eq!(v.capacity(), 16); + /// ``` + #[expect(clippy::inline_always, reason = "this method is trivial")] + #[inline(always)] + #[must_use] + pub const fn capacity(&self) -> usize { + CAP + } + + /// Returns the remaining capacity. + /// + /// # Examples + /// + /// ``` + /// use stack_collections::StackVec; + /// + /// let mut vec = StackVec::::new(); + /// assert_eq!(vec.remaining_capacity(), 5); + /// + /// vec.push(1); + /// vec.push(2); + /// assert_eq!(vec.remaining_capacity(), 3); + /// + /// vec.extend_from_slice(&[3, 4, 5]); + /// assert_eq!(vec.remaining_capacity(), 0); + /// assert!(vec.is_full()); + /// ``` + #[must_use] + #[inline] + pub const fn remaining_capacity(&self) -> usize { + CAP - self.len + } + + /// Returns `true` if the vector is empty. + /// + /// # Examples + /// + /// ``` + /// use stack_collections::StackVec; + /// + /// let mut vec = StackVec::::new(); + /// assert!(vec.is_empty()); + /// vec.push(1); + /// assert!(!vec.is_empty()); + /// ``` + #[must_use] + #[inline] + pub const fn is_empty(&self) -> bool { + self.len == 0 + } + + /// Returns `true` if the vector is at full capacity. + /// + /// # Examples + /// + /// ``` + /// use stack_collections::StackVec; + /// + /// let mut vec = StackVec::::new(); + /// assert!(!vec.is_full()); + /// vec.push(1); + /// vec.push(2); + /// assert!(vec.is_full()); + /// ``` + #[must_use] + #[inline] + pub const fn is_full(&self) -> bool { + self.len >= CAP + } + /// Returns a raw pointer to the vector's buffer. /// /// The pointer is valid for reads of `self.len()` elements. @@ -181,113 +317,204 @@ impl StackVec { self.len = new_len; } - // push - define_variants! { - fn push(self: &mut Self, value: T) -> (), - - normal_brief: "Appends a value", - try_brief: "Attempts to append a value", - unchecked_brief_suffix: "without bound checking", - ub_conditions: { - self.is_full() => "buffer capacity exceeded", - }, - prefixes: { - normal: {pub const}, - unchecked: {pub const}, - try: {pub}, - }, - unchecked_fn: push_unchecked, - try_fn: try_push, - body: { - // SAFETY: Caller guarantees self.len < CAP - let dst = unsafe { self.as_mut_ptr().add(self.len) }; - // SAFETY: Writing to valid uninitialized slot - unsafe { - dst.write(value); - } - self.len += 1; - }, - examples: { - normal: { - /// Basic usage: - /// - /// ``` - /// use stack_collections::StackVec; - /// - /// let mut vec = StackVec::::new(); - /// vec.push(1); - /// vec.push(2); - /// vec.push(3); - /// - /// assert_eq!(vec.len(), 3); - /// assert_eq!(vec[0], 1); - /// assert_eq!(vec[1], 2); - /// assert_eq!(vec[2], 3); - /// ``` - /// - /// A panic upon overflow: - /// - /// ```should_panic - /// use stack_collections::StackVec; - /// - /// let mut vec = StackVec::::new(); - /// vec.push(1); - /// vec.push(2); - /// - /// // this will panic at runtime - /// vec.push(3); - /// ``` - } - try: { - /// ``` - /// use stack_collections::StackVec; - /// - /// let mut vec = StackVec::::new(); - /// assert!(vec.try_push(1).is_some()); - /// assert!(vec.try_push(2).is_some()); - /// assert!(vec.try_push(3).is_none()); - /// - /// assert_eq!(vec.len(), 2); - /// assert_eq!(vec[0], 1); - /// assert_eq!(vec[1], 2); - /// ``` - } + /// Truncates the vector to the specified length. + /// + /// Does nothing if `len >= self.len()`. + /// + /// # Examples + /// + /// ``` + /// use stack_collections::StackVec; + /// + /// let mut vec = StackVec::::new(); + /// vec.push(1); + /// vec.push(2); + /// vec.push(3); + /// + /// vec.truncate(2); + /// assert_eq!(vec.len(), 2); + /// assert_eq!(vec[0], 1); + /// assert_eq!(vec[1], 2); + /// + /// vec.truncate(10); + /// assert_eq!(vec.len(), 2); + /// ``` + #[inline] + pub fn truncate(&mut self, len: usize) { + if len > self.len { + return; } + self.drop_elements(len); + self.len = len; } - // insert - define_variants! { - fn insert(self: &mut Self, index: usize, element: T) -> (), + /// Returns the contents as a slice. + /// + /// # Examples + /// + /// ``` + /// use stack_collections::StackVec; + /// + /// let mut vec = StackVec::::new(); + /// vec.extend_from_slice(&[1, 2, 3]); + /// assert_eq!(vec.as_slice(), &[1, 2, 3]); + /// ``` + #[must_use] + #[inline] + pub const fn as_slice(&self) -> &[T] { + // SAFETY: First self.len elements are initialized + unsafe { slice::from_raw_parts(self.as_ptr(), self.len) } + } - normal_brief: "Inserts an element at position `index`, shifting all elements after it", - try_brief: "Attempts to insert an element at `index`, shifting all elements after it", - unchecked_brief_suffix: "without bound or capacity checking", + /// Returns the contents as a mutable slice. + /// + /// # Examples + /// + /// ``` + /// use stack_collections::StackVec; + /// + /// let mut vec = StackVec::::new(); + /// vec.extend_from_slice(&[1, 2, 3]); + /// let slice = vec.as_mut_slice(); + /// slice[1] = 42; + /// assert_eq!(vec[1], 42); + /// ``` + #[must_use] + #[inline] + pub const fn as_mut_slice(&mut self) -> &mut [T] { + // SAFETY: First self.len elements are initialized + unsafe { slice::from_raw_parts_mut(self.as_mut_ptr(), self.len) } + } + + /// Returns an iterator over the slice. + /// + /// The iterator yields all items from start to end. + /// + /// # Examples + /// + /// ``` + /// use stack_collections::StackVec; + /// + /// let mut vec = StackVec::::new(); + /// vec.push(1); + /// vec.push(2); + /// vec.push(3); + /// + /// let sum: i32 = vec.iter().sum(); + /// assert_eq!(sum, 6); + /// ``` + #[inline] + pub fn iter(&self) -> slice::Iter<'_, T> { + self.as_slice().iter() + } + + /// Returns an iterator that allows modifying each value. + /// + /// The iterator yields all items from start to end. + /// + /// # Examples + /// + /// ``` + /// use stack_collections::StackVec; + /// + /// let mut vec = StackVec::::new(); + /// vec.push(1); + /// vec.push(2); + /// vec.push(3); + /// + /// for x in vec.iter_mut() { + /// *x *= 2; + /// } + /// assert_eq!(vec[0], 2); + /// assert_eq!(vec[1], 4); + /// assert_eq!(vec[2], 6); + /// ``` + #[inline] + pub fn iter_mut(&mut self) -> slice::IterMut<'_, T> { + self.as_mut_slice().iter_mut() + } + + /// Retains only elements that satisfy the predicate. + /// + /// # Examples + /// + /// ``` + /// use stack_collections::StackVec; + /// + /// let mut vec = StackVec::::new(); + /// vec.extend_from_slice(&[1, 2, 3, 4, 5]); + /// + /// // Keep only numbers greater than 2 + /// vec.retain(|x| *x > 2); + /// assert_eq!(vec.as_slice(), &[3, 4, 5]); + /// + /// // Keep all (no-op) + /// vec.retain(|_| true); + /// assert_eq!(vec.as_slice(), &[3, 4, 5]); + /// + /// // Remove all + /// vec.retain(|_| false); + /// assert!(vec.is_empty()); + /// ``` + #[inline] + pub fn retain(&mut self, mut f: F) + where + F: FnMut(&mut T) -> bool, + { + let base_ptr = self.as_mut_ptr(); + let mut kept = 0; + + for i in 0..self.len { + // SAFETY: i < self.len, so this is a valid and initialized element + let ptr = unsafe { base_ptr.add(i) }; + // SAFETY: ptr points to valid initialized element + let elem = unsafe { &mut *ptr }; + + if f(elem) { + if kept != i { + // SAFETY: kept < i < self.len, ranges don't overlap + let dst = unsafe { base_ptr.add(kept) }; + // SAFETY: Copying single element from valid source to valid dest + unsafe { + ptr::copy_nonoverlapping(ptr, dst, 1); + } + } + kept += 1; + } else { + // SAFETY: Dropping initialized element that won't be kept + unsafe { + ptr::drop_in_place(elem); + } + } + } + + self.len = kept; + } + + // index + define_variants! { + fn index(self: &Self, index: usize) -> &T, + + normal_brief: "Returns a reference to the element at `index`", + try_brief: "Attempts to return a reference to the element at `index`", + unchecked_brief_suffix: "without bounds checking", ub_conditions: { - index > self.len() => "index out of bounds", - self.is_full() => "buffer capacity exceeded", + index >= self.len() => "index out of bounds", }, prefixes: { normal: {pub const}, unchecked: {pub const}, - try: {pub}, + try: {pub const}, }, - - unchecked_fn: insert_unchecked, - try_fn: try_insert, + unchecked_fn: get_unchecked, + try_fn: get, body: { - // SAFETY: Caller guarantees index <= self.len - let src = unsafe { self.as_mut_ptr().add(index) }; - // SAFETY: Computing destination for shifted elements - let shifted_src = unsafe { src.add(1) }; - // SAFETY: Shifting elements right by 1; ranges are valid - unsafe { - ptr::copy(src, shifted_src, self.len - index); - } - // SAFETY: Writing element to valid position - unsafe { - ptr::write(src, element); - } - self.len += 1; + // SAFETY: Caller guarantees index < self.len + let ptr = unsafe { self.buf.as_ptr().add(index) }; + // SAFETY: Creating reference to initialized element + let elem_ref = unsafe { &*ptr }; + // SAFETY: Element at index is initialized + unsafe { elem_ref.assume_init_ref() } }, examples: { normal: { @@ -297,73 +524,45 @@ impl StackVec { /// use stack_collections::StackVec; /// /// let mut vec = StackVec::::new(); - /// vec.push(1); - /// vec.push(3); - /// assert_eq!(vec.len(), 2); + /// vec.extend_from_slice(&[10, 20, 30]); /// - /// vec.insert(1, 2); - /// assert_eq!(vec.len(), 3); - /// assert_eq!(vec[0], 1); - /// assert_eq!(vec[1], 2); - /// assert_eq!(vec[2], 3); + /// assert_eq!(*vec.index(0), 10); + /// assert_eq!(*vec.index(1), 20); + /// assert_eq!(*vec.index(2), 30); /// ``` /// - /// A panic if the index is out of bounds: + /// Panics on out-of-bounds: /// /// ```should_panic /// use stack_collections::StackVec; - /// /// let mut vec = StackVec::::new(); - /// vec.push(40); - /// assert_eq!(vec.len(), 1); - /// - /// // this will panic at runtime - /// vec.insert(2, 10); - /// ``` - /// - /// A panic upon overflow: - /// - /// ```should_panic - /// use stack_collections::StackVec; - /// - /// let mut vec = StackVec::::new(); - /// vec.push(40); - /// vec.push(50); - /// assert!(vec.is_full()); /// /// // this will panic at runtime - /// vec.insert(1, 19); + /// vec.index(0); /// ``` } try: { /// ``` /// use stack_collections::StackVec; /// - /// let mut vec = StackVec::::new(); + /// let mut vec = StackVec::::new(); /// vec.push(10); /// vec.push(20); + /// vec.push(30); /// - /// assert_eq!(vec.len(), 2); - /// - /// // index out of bounds - /// assert!(vec.try_insert(3, 30).is_none()); - /// - /// assert!(vec.try_insert(1, 30).is_some()); - /// assert_eq!(vec.len(), 3); - /// - /// // vector is full - /// assert!(vec.try_insert(3, 10).is_none()); + /// assert_eq!(vec.get(0), Some(&10)); + /// assert_eq!(vec.get(10), None); /// ``` } } } - // remove + // index_mut define_variants! { - fn remove(self: &mut Self, index: usize) -> T, + fn index_mut(self: &mut Self, index: usize) -> &mut T, - normal_brief: "Removes and returns the element at `index`", - try_brief: "Attempts to remove and return the element at `index`", + normal_brief: "Returns a mutable reference to the element at `index`", + try_brief: "Attempts to return a mutable reference to the element at `index`", unchecked_brief_suffix: "without bounds checking", ub_conditions: { index >= self.len() => "index out of bounds", @@ -373,21 +572,15 @@ impl StackVec { unchecked: {pub const}, try: {pub const}, }, - unchecked_fn: remove_unchecked, - try_fn: try_remove, + unchecked_fn: get_unchecked_mut, + try_fn: get_mut, body: { // SAFETY: Caller guarantees index < self.len - let ptr_to_remove = unsafe { self.as_mut_ptr().add(index) }; - // SAFETY: Reading initialized element at valid index - let result = unsafe { ptr::read(ptr_to_remove) }; - // SAFETY: Computing source pointer for shift - let src = unsafe { ptr_to_remove.add(1) }; - // SAFETY: Shifting remaining elements left by 1 - unsafe { - ptr::copy(src, ptr_to_remove, self.len - index - 1); - } - self.len -= 1; - result + let ptr = unsafe { self.buf.as_mut_ptr().add(index) }; + // SAFETY: Creating mutable reference to initialized element + let elem_ref = unsafe { &mut *ptr }; + // SAFETY: Element at index is initialized + unsafe { elem_ref.assume_init_mut() } }, examples: { normal: { @@ -397,27 +590,20 @@ impl StackVec { /// use stack_collections::StackVec; /// /// let mut vec = StackVec::::new(); - /// vec.push(1); - /// vec.push(2); - /// vec.push(3); + /// vec.extend_from_slice(&[10, 20, 30]); /// - /// let removed = vec.remove(1); - /// assert_eq!(removed, 2); - /// assert_eq!(vec.len(), 2); - /// assert_eq!(vec[0], 1); - /// assert_eq!(vec[1], 3); + /// *vec.index_mut(1) = 42; + /// assert_eq!(vec[1], 42); /// ``` /// - /// A panic if the index is out of bounds: + /// Panics on out-of-bounds: /// /// ```should_panic /// use stack_collections::StackVec; - /// /// let mut vec = StackVec::::new(); - /// vec.push(1); /// /// // this will panic at runtime - /// vec.remove(1); + /// vec.index_mut(0); /// ``` } try: { @@ -426,9 +612,15 @@ impl StackVec { /// /// let mut vec = StackVec::::new(); /// vec.push(10); + /// vec.push(20); + /// vec.push(30); /// - /// assert_eq!(vec.try_remove(0), Some(10)); - /// assert_eq!(vec.try_remove(0), None); + /// if let Some(val) = vec.get_mut(1) { + /// *val = 42; + /// } + /// assert_eq!(vec[1], 42); + /// + /// assert_eq!(vec.get_mut(10), None); /// ``` } } @@ -503,40 +695,31 @@ impl StackVec { } } - // swap_remove + // push define_variants! { - fn swap_remove(self: &mut Self, index: usize) -> T, + fn push(self: &mut Self, value: T) -> (), - normal_brief: "Removes and returns the element at `index` **without** shifting, replacing it with the last element (swap remove)", - try_brief: "Attempts to remove and return the element at `index` **without** shifting, replacing it with the last element (swap remove)", + normal_brief: "Appends a value", + try_brief: "Attempts to append a value", unchecked_brief_suffix: "without bound checking", ub_conditions: { - index >= self.len() => "index out of bounds", + self.is_full() => "buffer capacity exceeded", }, prefixes: { normal: {pub const}, unchecked: {pub const}, - try: {pub const}, + try: {pub}, }, - unchecked_fn: swap_remove_unchecked, - try_fn: try_swap_remove, + unchecked_fn: push_unchecked, + try_fn: try_push, body: { - // SAFETY: Caller guarantees index < self.len - let dst = unsafe { self.as_mut_ptr().add(index) }; - // SAFETY: Reading initialized element at valid index - let result = unsafe { ptr::read(dst) }; - self.len -= 1; - if index != self.len { - // SAFETY: Computing pointer to last element - let last_ptr = unsafe { self.as_ptr().add(self.len) }; - // SAFETY: Reading last element after decrementing len - let last = unsafe { last_ptr.read() }; - // SAFETY: Writing to previously read position - unsafe { - ptr::write(dst, last); - } + // SAFETY: Caller guarantees self.len < CAP + let dst = unsafe { self.as_mut_ptr().add(self.len) }; + // SAFETY: Writing to valid uninitialized slot + unsafe { + dst.write(value); } - result + self.len += 1; }, examples: { normal: { @@ -549,76 +732,76 @@ impl StackVec { /// vec.push(1); /// vec.push(2); /// vec.push(3); - /// vec.push(4); /// - /// // remove middle element - /// let removed = vec.swap_remove(1); - /// assert_eq!(removed, 2); /// assert_eq!(vec.len(), 3); /// assert_eq!(vec[0], 1); - /// assert_eq!(vec[1], 4); + /// assert_eq!(vec[1], 2); /// assert_eq!(vec[2], 3); - /// - /// // remove last element - /// let removed_last = vec.swap_remove(2); - /// assert_eq!(removed_last, 3); - /// assert_eq!(vec.len(), 2); - /// assert_eq!(vec[0], 1); - /// assert_eq!(vec[1], 4); /// ``` /// - /// A panic if the index is out of bounds: + /// A panic upon overflow: /// /// ```should_panic /// use stack_collections::StackVec; /// - /// let mut vec = StackVec::::new(); - /// vec.push(10); + /// let mut vec = StackVec::::new(); + /// vec.push(1); + /// vec.push(2); /// /// // this will panic at runtime - /// vec.swap_remove(1); + /// vec.push(3); /// ``` } try: { /// ``` /// use stack_collections::StackVec; /// - /// let mut vec = StackVec::::new(); - /// vec.extend_from_slice(&[1, 2, 3]); - /// - /// assert_eq!(vec.try_swap_remove(1), Some(2)); - /// assert_eq!(vec.as_slice(), &[1, 3]); + /// let mut vec = StackVec::::new(); + /// assert!(vec.try_push(1).is_some()); + /// assert!(vec.try_push(2).is_some()); + /// assert!(vec.try_push(3).is_none()); /// - /// assert_eq!(vec.try_swap_remove(10), None); + /// assert_eq!(vec.len(), 2); + /// assert_eq!(vec[0], 1); + /// assert_eq!(vec[1], 2); /// ``` } } } - // index + // insert define_variants! { - fn index(self: &Self, index: usize) -> &T, + fn insert(self: &mut Self, index: usize, element: T) -> (), - normal_brief: "Returns a reference to the element at `index`", - try_brief: "Attempts to return a reference to the element at `index`", - unchecked_brief_suffix: "without bounds checking", + normal_brief: "Inserts an element at position `index`, shifting all elements after it", + try_brief: "Attempts to insert an element at `index`, shifting all elements after it", + unchecked_brief_suffix: "without bound or capacity checking", ub_conditions: { - index >= self.len => "index out of bounds", + index > self.len() => "index out of bounds", + self.is_full() => "buffer capacity exceeded", }, prefixes: { normal: {pub const}, unchecked: {pub const}, - try: {pub const}, + try: {pub}, }, - unchecked_fn: get_unchecked, - try_fn: get, + + unchecked_fn: insert_unchecked, + try_fn: try_insert, body: { - // SAFETY: Caller guarantees index < self.len - let ptr = unsafe { self.buf.as_ptr().add(index) }; - // SAFETY: Creating reference to initialized element - let elem_ref = unsafe { &*ptr }; - // SAFETY: Element at index is initialized - unsafe { elem_ref.assume_init_ref() } + // SAFETY: Caller guarantees index <= self.len + let src = unsafe { self.as_mut_ptr().add(index) }; + // SAFETY: Computing destination for shifted elements + let shifted_src = unsafe { src.add(1) }; + // SAFETY: Shifting elements right by 1; ranges are valid + unsafe { + ptr::copy(src, shifted_src, self.len - index); + } + // SAFETY: Writing element to valid position + unsafe { + ptr::write(src, element); + } + self.len += 1; }, examples: { normal: { @@ -628,63 +811,97 @@ impl StackVec { /// use stack_collections::StackVec; /// /// let mut vec = StackVec::::new(); - /// vec.extend_from_slice(&[10, 20, 30]); + /// vec.push(1); + /// vec.push(3); + /// assert_eq!(vec.len(), 2); /// - /// assert_eq!(*vec.index(0), 10); - /// assert_eq!(*vec.index(1), 20); - /// assert_eq!(*vec.index(2), 30); + /// vec.insert(1, 2); + /// assert_eq!(vec.len(), 3); + /// assert_eq!(vec[0], 1); + /// assert_eq!(vec[1], 2); + /// assert_eq!(vec[2], 3); /// ``` /// - /// Panics on out-of-bounds: + /// A panic if the index is out of bounds: /// /// ```should_panic /// use stack_collections::StackVec; + /// /// let mut vec = StackVec::::new(); + /// vec.push(40); + /// assert_eq!(vec.len(), 1); /// /// // this will panic at runtime - /// vec.index(0); + /// vec.insert(2, 10); + /// ``` + /// + /// A panic upon overflow: + /// + /// ```should_panic + /// use stack_collections::StackVec; + /// + /// let mut vec = StackVec::::new(); + /// vec.push(40); + /// vec.push(50); + /// assert!(vec.is_full()); + /// + /// // this will panic at runtime + /// vec.insert(1, 19); /// ``` } try: { /// ``` /// use stack_collections::StackVec; /// - /// let mut vec = StackVec::::new(); + /// let mut vec = StackVec::::new(); /// vec.push(10); /// vec.push(20); - /// vec.push(30); /// - /// assert_eq!(vec.get(0), Some(&10)); - /// assert_eq!(vec.get(10), None); + /// assert_eq!(vec.len(), 2); + /// + /// // index out of bounds + /// assert!(vec.try_insert(3, 30).is_none()); + /// + /// assert!(vec.try_insert(1, 30).is_some()); + /// assert_eq!(vec.len(), 3); + /// + /// // vector is full + /// assert!(vec.try_insert(3, 10).is_none()); /// ``` } } } - // index_mut + // remove define_variants! { - fn index_mut(self: &mut Self, index: usize) -> &mut T, + fn remove(self: &mut Self, index: usize) -> T, - normal_brief: "Returns a mutable reference to the element at `index`", - try_brief: "Attempts to return a mutable reference to the element at `index`", + normal_brief: "Removes and returns the element at `index`", + try_brief: "Attempts to remove and return the element at `index`", unchecked_brief_suffix: "without bounds checking", ub_conditions: { - index >= self.len => "index out of bounds", + index >= self.len() => "index out of bounds", }, prefixes: { normal: {pub const}, unchecked: {pub const}, try: {pub const}, }, - unchecked_fn: get_unchecked_mut, - try_fn: get_mut, + unchecked_fn: remove_unchecked, + try_fn: try_remove, body: { // SAFETY: Caller guarantees index < self.len - let ptr = unsafe { self.buf.as_mut_ptr().add(index) }; - // SAFETY: Creating mutable reference to initialized element - let elem_ref = unsafe { &mut *ptr }; - // SAFETY: Element at index is initialized - unsafe { elem_ref.assume_init_mut() } + let ptr_to_remove = unsafe { self.as_mut_ptr().add(index) }; + // SAFETY: Reading initialized element at valid index + let result = unsafe { ptr::read(ptr_to_remove) }; + // SAFETY: Computing source pointer for shift + let src = unsafe { ptr_to_remove.add(1) }; + // SAFETY: Shifting remaining elements left by 1 + unsafe { + ptr::copy(src, ptr_to_remove, self.len - index - 1); + } + self.len -= 1; + result }, examples: { normal: { @@ -694,20 +911,27 @@ impl StackVec { /// use stack_collections::StackVec; /// /// let mut vec = StackVec::::new(); - /// vec.extend_from_slice(&[10, 20, 30]); + /// vec.push(1); + /// vec.push(2); + /// vec.push(3); /// - /// *vec.index_mut(1) = 42; - /// assert_eq!(vec[1], 42); + /// let removed = vec.remove(1); + /// assert_eq!(removed, 2); + /// assert_eq!(vec.len(), 2); + /// assert_eq!(vec[0], 1); + /// assert_eq!(vec[1], 3); /// ``` /// - /// Panics on out-of-bounds: + /// A panic if the index is out of bounds: /// /// ```should_panic /// use stack_collections::StackVec; + /// /// let mut vec = StackVec::::new(); + /// vec.push(1); /// /// // this will panic at runtime - /// vec.index_mut(0); + /// vec.remove(1); /// ``` } try: { @@ -716,49 +940,137 @@ impl StackVec { /// /// let mut vec = StackVec::::new(); /// vec.push(10); - /// vec.push(20); - /// vec.push(30); /// - /// if let Some(val) = vec.get_mut(1) { - /// *val = 42; - /// } - /// assert_eq!(vec[1], 42); - /// - /// assert_eq!(vec.get_mut(10), None); + /// assert_eq!(vec.try_remove(0), Some(10)); + /// assert_eq!(vec.try_remove(0), None); /// ``` } } } + // swap_remove define_variants! { - fn extend_from_slice(self: &mut Self, slice: &[T]) -> (), - where_clause: { T: Clone } + fn swap_remove(self: &mut Self, index: usize) -> T, - normal_brief: "Extends the vector from a slice", - try_brief: "Attempts to extend the vector from a slice", - unchecked_brief_suffix: "without capacity checking", + normal_brief: "Removes and returns the element at `index` **without** shifting, replacing it with the last element (swap remove)", + try_brief: "Attempts to remove and return the element at `index` **without** shifting, replacing it with the last element (swap remove)", + unchecked_brief_suffix: "without bound checking", ub_conditions: { - self.len() + slice.len() > CAP => "buffer capacity exceeded", + index >= self.len() => "index out of bounds", }, prefixes: { - normal: {pub}, - unchecked: {pub}, - try: {pub}, + normal: {pub const}, + unchecked: {pub const}, + try: {pub const}, }, - unchecked_fn: extend_from_slice_unchecked, - try_fn: try_extend_from_slice, + unchecked_fn: swap_remove_unchecked, + try_fn: try_swap_remove, body: { - if size_of::() == 0 || mem::needs_drop::() { - for item in slice { - // SAFETY: Caller guarantees sufficient capacity - unsafe { - self.push_unchecked(item.clone()); - } - } - } else { - // SAFETY: Computing destination pointer at end of initialized elements - let dst = unsafe { self.as_mut_ptr().add(self.len) }; - // SAFETY: Caller guarantees capacity; copying slice.len() elements + // SAFETY: Caller guarantees index < self.len + let dst = unsafe { self.as_mut_ptr().add(index) }; + // SAFETY: Reading initialized element at valid index + let result = unsafe { ptr::read(dst) }; + self.len -= 1; + if index != self.len { + // SAFETY: Computing pointer to last element + let last_ptr = unsafe { self.as_ptr().add(self.len) }; + // SAFETY: Reading last element after decrementing len + let last = unsafe { last_ptr.read() }; + // SAFETY: Writing to previously read position + unsafe { + ptr::write(dst, last); + } + } + result + }, + examples: { + normal: { + /// Basic usage: + /// + /// ``` + /// use stack_collections::StackVec; + /// + /// let mut vec = StackVec::::new(); + /// vec.push(1); + /// vec.push(2); + /// vec.push(3); + /// vec.push(4); + /// + /// // remove middle element + /// let removed = vec.swap_remove(1); + /// assert_eq!(removed, 2); + /// assert_eq!(vec.len(), 3); + /// assert_eq!(vec[0], 1); + /// assert_eq!(vec[1], 4); + /// assert_eq!(vec[2], 3); + /// + /// // remove last element + /// let removed_last = vec.swap_remove(2); + /// assert_eq!(removed_last, 3); + /// assert_eq!(vec.len(), 2); + /// assert_eq!(vec[0], 1); + /// assert_eq!(vec[1], 4); + /// ``` + /// + /// A panic if the index is out of bounds: + /// + /// ```should_panic + /// use stack_collections::StackVec; + /// + /// let mut vec = StackVec::::new(); + /// vec.push(10); + /// + /// // this will panic at runtime + /// vec.swap_remove(1); + /// ``` + } + try: { + /// ``` + /// use stack_collections::StackVec; + /// + /// let mut vec = StackVec::::new(); + /// vec.extend_from_slice(&[1, 2, 3]); + /// + /// assert_eq!(vec.try_swap_remove(1), Some(2)); + /// assert_eq!(vec.as_slice(), &[1, 3]); + /// + /// assert_eq!(vec.try_swap_remove(10), None); + /// ``` + } + } + } +} + +impl StackVec { + // extend_from_slice + define_variants! { + fn extend_from_slice(self: &mut Self, slice: &[T]) -> (), + + normal_brief: "Extends the vector from a slice", + try_brief: "Attempts to extend the vector from a slice", + unchecked_brief_suffix: "without capacity checking", + ub_conditions: { + self.len() + slice.len() > CAP => "buffer capacity exceeded", + }, + prefixes: { + normal: {pub}, + unchecked: {pub}, + try: {pub}, + }, + unchecked_fn: extend_from_slice_unchecked, + try_fn: try_extend_from_slice, + body: { + if size_of::() == 0 || mem::needs_drop::() { + for item in slice { + // SAFETY: Caller guarantees sufficient capacity + unsafe { + self.push_unchecked(item.clone()); + } + } + } else { + // SAFETY: Computing destination pointer at end of initialized elements + let dst = unsafe { self.as_mut_ptr().add(self.len) }; + // SAFETY: Caller guarantees capacity; copying slice.len() elements unsafe { ptr::copy_nonoverlapping(slice.as_ptr(), dst, slice.len()); } @@ -808,315 +1120,6 @@ impl StackVec { } } } - - /// Retains only elements that satisfy the predicate. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackVec; - /// - /// let mut vec = StackVec::::new(); - /// vec.extend_from_slice(&[1, 2, 3, 4, 5]); - /// - /// // Keep only numbers greater than 2 - /// vec.retain(|x| *x > 2); - /// assert_eq!(vec.as_slice(), &[3, 4, 5]); - /// - /// // Keep all (no-op) - /// vec.retain(|_| true); - /// assert_eq!(vec.as_slice(), &[3, 4, 5]); - /// - /// // Remove all - /// vec.retain(|_| false); - /// assert!(vec.is_empty()); - /// ``` - #[inline] - pub fn retain(&mut self, mut f: F) - where - F: FnMut(&mut T) -> bool, - { - let base_ptr = self.as_mut_ptr(); - let mut kept = 0; - - for i in 0..self.len { - // SAFETY: i < self.len, so this is a valid and initialized element - let ptr = unsafe { base_ptr.add(i) }; - // SAFETY: ptr points to valid initialized element - let elem = unsafe { &mut *ptr }; - - if f(elem) { - if kept != i { - // SAFETY: kept < i < self.len, ranges don't overlap - let dst = unsafe { base_ptr.add(kept) }; - // SAFETY: Copying single element from valid source to valid dest - unsafe { - ptr::copy_nonoverlapping(ptr, dst, 1); - } - } - kept += 1; - } else { - // SAFETY: Dropping initialized element that won't be kept - unsafe { - ptr::drop_in_place(elem); - } - } - } - - self.len = kept; - } - /// Returns the contents as a slice. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackVec; - /// - /// let mut vec = StackVec::::new(); - /// vec.extend_from_slice(&[1, 2, 3]); - /// assert_eq!(vec.as_slice(), &[1, 2, 3]); - /// ``` - #[must_use] - #[inline] - pub const fn as_slice(&self) -> &[T] { - // SAFETY: First self.len elements are initialized - unsafe { slice::from_raw_parts(self.as_ptr(), self.len) } - } - - /// Returns the contents as a mutable slice. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackVec; - /// - /// let mut vec = StackVec::::new(); - /// vec.extend_from_slice(&[1, 2, 3]); - /// let slice = vec.as_mut_slice(); - /// slice[1] = 42; - /// assert_eq!(vec[1], 42); - /// ``` - #[must_use] - #[inline] - pub const fn as_mut_slice(&mut self) -> &mut [T] { - // SAFETY: First self.len elements are initialized - unsafe { slice::from_raw_parts_mut(self.as_mut_ptr(), self.len) } - } - - /// Returns an iterator over the slice. - /// - /// The iterator yields all items from start to end. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackVec; - /// - /// let mut vec = StackVec::::new(); - /// vec.push(1); - /// vec.push(2); - /// vec.push(3); - /// - /// let sum: i32 = vec.iter().sum(); - /// assert_eq!(sum, 6); - /// ``` - #[inline] - pub fn iter(&self) -> slice::Iter<'_, T> { - self.as_slice().iter() - } - - /// Returns an iterator that allows modifying each value. - /// - /// The iterator yields all items from start to end. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackVec; - /// - /// let mut vec = StackVec::::new(); - /// vec.push(1); - /// vec.push(2); - /// vec.push(3); - /// - /// for x in vec.iter_mut() { - /// *x *= 2; - /// } - /// assert_eq!(vec[0], 2); - /// assert_eq!(vec[1], 4); - /// assert_eq!(vec[2], 6); - /// ``` - #[inline] - pub fn iter_mut(&mut self) -> slice::IterMut<'_, T> { - self.as_mut_slice().iter_mut() - } - - /// Drops all initialized elements in the vector without resetting `self.len`. - #[inline] - fn drop_elements(&mut self, start: usize) { - if size_of::() == 0 || mem::needs_drop::() { - for i in start..self.len { - // SAFETY: i < self.len, so element is initialized - let elem = unsafe { self.buf.get_unchecked_mut(i) }; - // SAFETY: elem points to initialized element - unsafe { - elem.assume_init_drop(); - } - } - } - } - - /// Truncates the vector to the specified length. - /// - /// Does nothing if `len >= self.len()`. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackVec; - /// - /// let mut vec = StackVec::::new(); - /// vec.push(1); - /// vec.push(2); - /// vec.push(3); - /// - /// vec.truncate(2); - /// assert_eq!(vec.len(), 2); - /// assert_eq!(vec[0], 1); - /// assert_eq!(vec[1], 2); - /// - /// vec.truncate(10); - /// assert_eq!(vec.len(), 2); - /// ``` - #[inline] - pub fn truncate(&mut self, len: usize) { - if len > self.len { - return; - } - self.drop_elements(len); - self.len = len; - } - - /// Clears the vector, dropping all initialized elements if necessary, - /// and resets the length to zero. - /// - /// For types `T` that implement [`Copy`] or do not require [`Drop`], this is - /// effectively just setting `len = 0` without running any destructors. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackVec; - /// - /// let mut vec = StackVec::::new(); - /// vec.push(1); - /// vec.push(2); - /// - /// vec.clear(); - /// assert_eq!(vec.len(), 0); - /// assert!(vec.is_empty()); - /// ``` - #[inline] - pub fn clear(&mut self) { - self.drop_elements(0); - self.len = 0; - } - - /// Returns the current length. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackVec; - /// - /// let mut vec = StackVec::::new(); - /// assert_eq!(vec.len(), 0); - /// vec.push(1); - /// assert_eq!(vec.len(), 1); - /// ``` - #[inline] - #[must_use] - pub const fn len(&self) -> usize { - self.len - } - - /// Returns the capacity. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackVec; - /// - /// let v = StackVec::::new(); - /// assert_eq!(v.capacity(), 16); - /// ``` - #[expect(clippy::inline_always, reason = "this method is trivial")] - #[inline(always)] - #[must_use] - pub const fn capacity(&self) -> usize { - CAP - } - - /// Returns the remaining capacity. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackVec; - /// - /// let mut vec = StackVec::::new(); - /// assert_eq!(vec.remaining_capacity(), 5); - /// - /// vec.push(1); - /// vec.push(2); - /// assert_eq!(vec.remaining_capacity(), 3); - /// - /// vec.extend_from_slice(&[3, 4, 5]); - /// assert_eq!(vec.remaining_capacity(), 0); - /// assert!(vec.is_full()); - /// ``` - #[must_use] - #[inline] - pub const fn remaining_capacity(&self) -> usize { - CAP - self.len - } - - /// Returns `true` if the vector is empty. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackVec; - /// - /// let mut vec = StackVec::::new(); - /// assert!(vec.is_empty()); - /// vec.push(1); - /// assert!(!vec.is_empty()); - /// ``` - #[must_use] - #[inline] - pub const fn is_empty(&self) -> bool { - self.len == 0 - } - - /// Returns `true` if the vector is at full capacity. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackVec; - /// - /// let mut vec = StackVec::::new(); - /// assert!(!vec.is_full()); - /// vec.push(1); - /// vec.push(2); - /// assert!(vec.is_full()); - /// ``` - #[must_use] - #[inline] - pub const fn is_full(&self) -> bool { - self.len >= CAP - } } impl Default for StackVec {