From 1e92b4a5791d96b35e8300c02cdea2d4be85491b Mon Sep 17 00:00:00 2001 From: OverwrittenCode Date: Sat, 4 Oct 2025 23:21:40 +0100 Subject: [PATCH 1/8] feat: add new API method(s) refactor: overhaul on code structure - split structures into their own files - enforce code style - make define_variant macro more flexible feat: add alloc feature guard docs: cleanup documentation chore: add rustfmt.toml chore: modify rust.yml chore: bump version to 0.3.0 --- .github/workflows/rust.yml | 185 ++- Cargo.toml | 5 +- README.md | 32 +- rustfmt.toml | 1 + src/lib.rs | 3202 +----------------------------------- src/stack_string.rs | 1585 ++++++++++++++++++ src/stack_vec.rs | 1833 +++++++++++++++++++++ 7 files changed, 3649 insertions(+), 3194 deletions(-) create mode 100644 rustfmt.toml create mode 100644 src/stack_string.rs create mode 100644 src/stack_vec.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 8f802f0..06307df 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -1,4 +1,4 @@ -name: Rust CI +name: CI on: push: @@ -6,62 +6,177 @@ on: pull_request: branches: [ "main" ] +permissions: + contents: read + env: CARGO_TERM_COLOR: always + RUSTFLAGS: -Dwarnings jobs: - build-and-docs: + # Test on stable with all feature combinations + stable: + name: Stable (${{ matrix.name }}) runs-on: ubuntu-latest - + timeout-minutes: 45 + strategy: + fail-fast: false + matrix: + include: + - name: "no_std" + flags: "--no-default-features" + - name: "alloc" + flags: "--no-default-features --features alloc" + - name: "std" + flags: "--features std" + - name: "default" + flags: "" steps: - # Checkout code - uses: actions/checkout@v4 - # Cache Cargo registry and git sources - - name: Cache Cargo registry - uses: actions/cache@v3 + - name: Cache Cargo + uses: actions/cache@v4 with: path: | ~/.cargo/registry ~/.cargo/git - key: cargo-registry-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }} - restore-keys: cargo-registry-${{ runner.os }}- + target + key: ${{ runner.os }}-cargo-stable-${{ hashFiles('**/Cargo.lock') }}-${{ matrix.name }} + restore-keys: | + ${{ runner.os }}-cargo-stable-${{ hashFiles('**/Cargo.lock') }}- + ${{ runner.os }}-cargo-stable- + ${{ runner.os }}-cargo- + + - uses: dtolnay/rust-toolchain@stable - # Cache Cargo build artifacts - - name: Cache Cargo target directory - uses: actions/cache@v3 + - name: Build + run: cargo build ${{ matrix.flags }} --verbose + + - name: Test + run: cargo test ${{ matrix.flags }} --verbose + + # Test on nightly with nightly feature + nightly: + name: Nightly (${{ matrix.name }}) + runs-on: ubuntu-latest + timeout-minutes: 45 + strategy: + fail-fast: false + matrix: + include: + - name: "no_std" + flags: "--no-default-features" + - name: "alloc" + flags: "--no-default-features --features alloc" + - name: "std+nightly" + flags: "--features std,nightly" + - name: "all-features" + flags: "--all-features" + steps: + - uses: actions/checkout@v4 + + - name: Cache Cargo + uses: actions/cache@v4 with: - path: target - key: cargo-target-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }} - restore-keys: cargo-target-${{ runner.os }}- + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-nightly-${{ hashFiles('**/Cargo.lock') }}-${{ matrix.name }} + restore-keys: | + ${{ runner.os }}-cargo-nightly-${{ hashFiles('**/Cargo.lock') }}- + ${{ runner.os }}-cargo-nightly- + ${{ runner.os }}-cargo- + + - uses: dtolnay/rust-toolchain@nightly + + - name: Build + run: cargo build ${{ matrix.flags }} --verbose + + - name: Test + run: cargo test ${{ matrix.flags }} --verbose + + # Clippy linting on all features + clippy: + name: Clippy + runs-on: ubuntu-latest + if: github.event_name != 'pull_request' + timeout-minutes: 45 + steps: + - uses: actions/checkout@v4 - # Install stable Rust for CI - - name: Install stable Rust - uses: dtolnay/rust-toolchain@stable + - uses: dtolnay/rust-toolchain@stable with: components: clippy - # Lint (excluding nightly feature) - - name: Clippy check - run: cargo clippy --all-targets --features std -- -D warnings + - name: Clippy (all features) + run: cargo clippy --all-targets --all-features -- -Dclippy::all -Dclippy::pedantic -Dclippy::nursery + + # Format check + fmt: + name: Rustfmt + runs-on: ubuntu-latest + timeout-minutes: 45 + steps: + - uses: actions/checkout@v4 - # Run tests (excluding nightly feature) - - name: Run tests - run: cargo test --features std --verbose + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt - # Build crate (excluding nightly feature) - - name: Build crate - run: cargo build --features std --verbose + - name: Check formatting + run: cargo fmt --all -- --check - - name: Build with no-default-features - run: cargo build --no-default-features --verbose + # Documentation + doc: + name: Documentation + runs-on: ubuntu-latest + timeout-minutes: 45 + env: + RUSTDOCFLAGS: -Dwarnings + steps: + - uses: actions/checkout@v4 - # --- Docs.rs using nightly --- - - name: Install nightly Rust - uses: dtolnay/rust-toolchain@nightly + - uses: dtolnay/rust-toolchain@nightly - - name: Install cargo-docs-rs - uses: dtolnay/install@cargo-docs-rs + - uses: dtolnay/install@cargo-docs-rs - name: Build docs.rs documentation - run: cargo docs-rs \ No newline at end of file + run: cargo docs-rs + + - name: Build docs (all features) + run: cargo doc --no-deps --all-features + + # Minimal versions + minimal: + name: Minimal versions + runs-on: ubuntu-latest + timeout-minutes: 45 + steps: + - uses: actions/checkout@v4 + + - uses: dtolnay/rust-toolchain@nightly + + - name: Generate minimal lockfile + run: cargo generate-lockfile -Z minimal-versions + + - name: Check with minimal versions + run: cargo check --locked --all-features + + # Miri for undefined behavior detection + miri: + name: Miri + runs-on: ubuntu-latest + timeout-minutes: 45 + steps: + - uses: actions/checkout@v4 + + - uses: dtolnay/rust-toolchain@miri + + - name: Setup Miri + run: cargo miri setup + + - name: Test with Miri (all features) + run: cargo miri test --all-features + env: + MIRIFLAGS: -Zmiri-strict-provenance \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 57535b7..107bd75 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "stack_collections" -version = "0.2.0" +version = "0.3.0" edition = "2024" authors = ["Kyle Thapar "] description = "Stack-allocated collections for Rust" @@ -18,5 +18,6 @@ rustdoc-args = ["--cfg", "docsrs"] [features] default = ["std"] -std = [] +std = ["alloc"] +alloc = [] nightly = [] diff --git a/README.md b/README.md index 60194ce..c0f2076 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Add this to your `Cargo.toml`: ```toml [dependencies] -stack_collections = "0.2.0" +stack_collections = "0.3.0" ``` ### StackString Example @@ -32,16 +32,16 @@ stack_collections = "0.2.0" use stack_collections::StackString; fn main() { - let mut s = StackString::<32>::new(); - s.push_str("Hello, "); - s.push_str("world!"); + let mut stack_string = StackString::<32>::new(); + stack_string.push_str("Hello, "); + stack_string.push_str("world!"); - println!("String: {}", s); - println!("Length: {}", s.len()); - println!("Capacity: {}", s.capacity()); + println!("String: {}", stack_string); + println!("Length: {}", stack_string.len()); + println!("Capacity: {}", stack_string.capacity()); // Pop characters - while let Some(c) = s.try_pop() { + while let Some(c) = stack_string.try_pop() { println!("Popped: {}", c); } } @@ -53,17 +53,17 @@ fn main() { use stack_collections::StackVec; fn main() { - let mut v = StackVec::::new(); + let mut vec = StackVec::::new(); for i in 1..=5 { - v.push(i); + vec.push(i); } - println!("Vector: {:?}", v); - println!("Sum: {}", v.iter().sum::()); + println!("Vector: {:?}", vec); + println!("Sum: {}", vec.iter().sum::()); - v.retain(|x| *x % 2 == 0); - println!("Even numbers: {:?}", v); + vec.retain(|x| *x < 4); + println!("Numbers less than 4: {:?}", vec); } ``` @@ -115,8 +115,8 @@ undefined behavior even when capacity is exceeded (operations will panic or retu Licensed under either of: -- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) -- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or ) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) at your option. diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..6f133e6 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +max_width = 120 \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 7f0988c..ff75aff 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,47 +1,55 @@ -//! stack_collections: Stack-allocated collections for Rust +//! `stack_collections`: Stack-allocated collections for Rust //! -//! Provides `StackString`, `StackVec`, and `StackArrayString`. +//! Provides [`StackString`], [`StackVec`], and [`StackArrayString`]. //! These are fixed-capacity, stack-allocated collections designed for //! `no_std` environments, zero heap allocations, predictable memory usage, //! and deterministic performance. - -#![deny(missing_docs)] -#![deny(clippy::missing_docs_in_private_items)] +#![deny(warnings)] +#![deny(clippy::all)] +#![deny(clippy::pedantic)] +#![deny(clippy::nursery)] +#![deny(clippy::undocumented_unsafe_blocks)] +#![deny(clippy::multiple_unsafe_ops_per_block)] +#![deny(clippy::semicolon_if_nothing_returned)] +#![deny(clippy::std_instead_of_core)] +#![deny(clippy::std_instead_of_alloc)] +#![deny(clippy::missing_inline_in_public_items)] +#![deny(clippy::return_self_not_must_use)] #![no_std] #![cfg_attr(feature = "std", allow(unused))] #![cfg_attr(feature = "nightly", feature(ascii_char))] #![cfg_attr(docsrs, feature(doc_cfg))] +#[cfg(feature = "alloc")] +extern crate alloc; #[cfg(feature = "std")] extern crate std; -use core::hash::{Hash, Hasher}; -use core::hint::unreachable_unchecked; -use core::iter::FromIterator; -use core::mem::MaybeUninit; -use core::ops::{Deref, DerefMut, Index, IndexMut}; -use core::slice; -use core::{fmt, mem, ptr, str}; - -#[cfg(feature = "std")] -use std::{ - borrow::ToOwned, - io::{self, Write}, - string::String, -}; +/// Internal module that are not meant for users to use. +mod internal { + /// Helper macro for creating an empty collection. + macro_rules! empty_collection { + () => { + Self { + // SAFETY: An array of MaybeUninit does not require initialization + buf: unsafe { MaybeUninit::uninit().assume_init() }, + len: 0, + } + }; + } -/// Helper macro to define unchecked, normal, and try variants of a method. -macro_rules! define_variants { + /// Helper macro to define unchecked, normal, and try variants of a method. + macro_rules! define_variants { ( $(#[$meta:meta])* - fn $name:ident(&mut $self:ident $(, $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, try_brief: $try_brief:literal, unchecked_brief_suffix: $unchecked_brief_suffix:literal, - checks: { - $($check:expr => $error:literal),+ $(,)? + ub_conditions: { + $($ub_condition:expr => $error:literal),+ $(,)? }, prefixes: { normal: {$($normal_prefix:tt)*}, @@ -50,9 +58,7 @@ macro_rules! define_variants { }, unchecked_fn: $unchecked_fn:ident, try_fn: $try_fn:ident, - - body: { $($body:tt)* }, - + body: $body:tt, $(examples: { normal: { $($ex_normal:tt)* } try: { $($ex_try:tt)* } @@ -61,26 +67,28 @@ macro_rules! define_variants { $(#[$meta])* #[doc = concat!(" ", $normal_brief, ", ", $unchecked_brief_suffix, ".")] /// - #[doc = concat!("See also [`Self::", stringify!($name), "`] for the safe version and [`Self::", stringify!($try_fn), "`] for the `Option` returning version.")] + #[doc = concat!(" See also [`Self::", stringify!($name), "`] for the safe version and [`Self::", stringify!($try_fn), "`] for the [`Option`] returning version.")] /// /// # Safety /// /// Calling this function when any of the following conditions are **`true`** is **undefined behavior**: - $( #[doc = concat!(" - `", stringify!($check), "`")] )+ + $( #[doc = concat!(" - `", stringify!($ub_condition), "`")] )+ #[inline] - $($unchecked_prefix)* unsafe fn $unchecked_fn(&mut $self $(, $param: $param_ty)*) $(-> $ret)? + $($unchecked_prefix)* unsafe fn $unchecked_fn($self: $self_ty $(, $param: $param_ty)*) $(-> $ret)? $(where $($where_clause)*)? { - $($body)* + $( debug_assert!(!($ub_condition), $error); )+ + $body } + $(#[$meta])* #[doc = concat!(" ", $normal_brief, ".")] /// - #[doc = concat!("See also [`Self::", stringify!($unchecked_fn), "`] for the unchecked version and [`Self::", stringify!($try_fn), "`] for the `Option` returning version.")] + #[doc = concat!(" See also [`Self::", stringify!($unchecked_fn), "`] for the unchecked version and [`Self::", stringify!($try_fn), "`] for the [`Option`] returning version.")] /// /// # Panics /// - $( #[doc = concat!(" - \"", $error, "\" if `", stringify!($check), "`")] )+ + $( #[doc = concat!(" - \"", $error, "\" if `", stringify!($ub_condition), "`")] )+ $( /// /// # Examples @@ -88,20 +96,21 @@ macro_rules! define_variants { $($ex_normal)* )? #[inline] - $($normal_prefix)* fn $name(&mut $self $(, $param: $param_ty)*) $(-> $ret)? + $($normal_prefix)* fn $name($self: $self_ty $(, $param: $param_ty)*) $(-> $ret)? $(where $($where_clause)*)? { - $( assert!(!($check), $error); )+ + $( assert!(!($ub_condition), $error); )+ + // SAFETY: passed all undefined behaviour conditions above unsafe { $self.$unchecked_fn($($param),*) } } $(#[$meta])* #[doc = concat!(" ", $try_brief, ".")] /// - #[doc = concat!("See also [`Self::", stringify!($name), "`] for the panic-on-error version and [`Self::", stringify!($unchecked_fn), "`] for the unchecked version.")] + #[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`**: - $( #[doc = concat!(" - `", stringify!($check), "`")] )+ + $( #[doc = concat!(" - `", stringify!($ub_condition), "`")] )+ $( /// /// # Examples @@ -110,18 +119,29 @@ macro_rules! define_variants { )? #[must_use] #[inline] - $($try_prefix)* fn $try_fn(&mut $self $(, $param: $param_ty)*) $(-> Option<$ret>)? + $($try_prefix)* fn $try_fn($self: $self_ty $(, $param: $param_ty)*) $(-> Option<$ret>)? $(where $($where_clause)*)? { - $( if $check { return None; } )+ + $( if $ub_condition { return None; } )+ + // SAFETY: passed all undefined behaviour conditions above let result = unsafe { $self.$unchecked_fn($($param),*) }; Some(result) } }; } -#[cfg(feature = "nightly")] -use core::ascii::Char; + pub(crate) use define_variants; + pub(crate) use empty_collection; +} + +/// A UTF-8–encoded, stack-allocated, fixed-capacity string. +pub mod stack_string; + +/// A stack-allocated vector with a fixed capacity. +pub mod stack_vec; + +pub use crate::stack_string::StackString; +pub use crate::stack_vec::StackVec; /// A stack-allocated array of small strings. /// @@ -145,3103 +165,3 @@ use core::ascii::Char; /// assert_eq!(arr[1].as_str(), "world"); /// ``` pub type StackArrayString = StackVec, CAP>; - -/// A UTF-8–encoded, stack-allocated, fixed-capacity string. -pub struct StackString { - /// Internal buffer holding up to `N` bytes of UTF-8 text. - buf: [MaybeUninit; N], - - /// Current string length (in bytes). - len: usize, -} - -impl StackString { - /// Creates a new `StackString`. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackString; - /// - /// let s = StackString::<32>::new(); - /// assert_eq!(s.len(), 0); - /// assert_eq!(s.capacity(), 32); - /// assert!(s.is_empty()); - /// assert!(!s.is_full()); - /// assert_eq!(s.as_str(), ""); - /// ``` - #[must_use] - #[inline] - pub const fn new() -> Self { - Self { - buf: unsafe { MaybeUninit::uninit().assume_init() }, - len: 0, - } - } - - /// Returns a raw pointer to the string's buffer. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackString; - /// - /// let mut s = StackString::<8>::new(); - /// s.push_str("hello"); - /// - /// let ptr = s.as_ptr(); - /// unsafe { - /// assert_eq!(*ptr, b'h'); - /// assert_eq!(*ptr.add(4), b'o'); - /// } - /// ``` - #[must_use] - #[inline] - pub const fn as_ptr(&self) -> *const u8 { - self.buf.as_ptr().cast::() - } - - /// Returns a mutable raw pointer to the string's buffer. - /// - /// # Safety - /// - /// It is your responsibility to make sure that the string slice only gets modified in a way that it remains valid UTF-8. - /// - /// For safe mutable access, use [`Self::as_mut_str()`] instead. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackString; - /// - /// let mut s = StackString::<8>::new(); - /// s.push_str("hello"); - /// - /// let ptr = s.as_mut_ptr(); - /// unsafe { - /// // SAFETY: converting 'h' to 'H' maintains UTF-8 validity - /// *ptr = b'H'; - /// } - /// assert_eq!(s.as_str(), "Hello"); - /// ``` - #[must_use] - #[inline] - pub const fn as_mut_ptr(&mut self) -> *mut u8 { - self.buf.as_mut_ptr().cast::() - } - - define_variants! { - fn pop(&mut self) -> char, - - normal_brief: "Removes and returns the last char", - try_brief: "Attempts to remove and return the last char", - unchecked_brief_suffix: "without bound checking", - checks: { - self.is_empty() => "string is empty", - }, - prefixes: { - normal: {pub const}, - unchecked: {pub const}, - try: {pub const}, - }, - unchecked_fn: pop_unchecked, - try_fn: try_pop, - body: { - const TAG_CONT_MASK: u8 = 0b1100_0000; - const TAG_CONT: u8 = 0b1000_0000; - - unsafe { - let mut pos = self.len - 1; - let ptr = self.buf.as_ptr().cast::(); - - while pos > 0 && (ptr.add(pos).read() & TAG_CONT_MASK) == TAG_CONT { - pos -= 1; - } - - let first_byte = ptr.add(pos).read(); - let code = match self.len - pos { - 1 => first_byte as u32, - 2 => { - let b1 = first_byte as u32; - let b2 = ptr.add(pos + 1).read() as u32; - ((b1 & 0x1F) << 6) | (b2 & 0x3F) - } - 3 => { - let b1 = first_byte as u32; - let b2 = ptr.add(pos + 1).read() as u32; - let b3 = ptr.add(pos + 2).read() as u32; - ((b1 & 0x0F) << 12) | ((b2 & 0x3F) << 6) | (b3 & 0x3F) - } - 4 => { - let b1 = first_byte as u32; - let b2 = ptr.add(pos + 1).read() as u32; - let b3 = ptr.add(pos + 2).read() as u32; - let b4 = ptr.add(pos + 3).read() as u32; - ((b1 & 0x07) << 18) | ((b2 & 0x3F) << 12) | ((b3 & 0x3F) << 6) | (b4 & 0x3F) - } - _ => unreachable_unchecked(), - }; - - self.len = pos; - char::from_u32_unchecked(code) - } - }, - examples: { - normal: { - /// Basic usage: - /// - /// ``` - /// use stack_collections::StackString; - /// - /// let mut s = StackString::<8>::new(); - /// s.push_str("abc"); - /// - /// assert_eq!(s.pop(), 'c'); - /// assert_eq!(s.as_str(), "ab"); - /// assert_eq!(s.len(), 2); - /// - /// assert_eq!(s.pop(), 'b'); - /// assert_eq!(s.pop(), 'a'); - /// assert!(s.is_empty()); - /// ``` - /// - /// Multibyte UTF-8 characters: - /// - /// ``` - /// use stack_collections::StackString; - /// - /// let mut s = StackString::<16>::new(); - /// s.push_str("hello😀world"); - /// - /// assert_eq!(s.pop(), 'd'); - /// assert_eq!(s.pop(), 'l'); - /// assert_eq!(s.pop(), 'r'); - /// assert_eq!(s.pop(), 'o'); - /// assert_eq!(s.pop(), 'w'); - /// assert_eq!(s.pop(), '😀'); - /// assert_eq!(s.as_str(), "hello"); - /// ``` - /// - /// A panic if the string is empty: - /// - ///```should_panic - /// use stack_collections::StackString; - /// - /// let mut s = StackString::<8>::new(); - /// - /// // this will panic at runtime - /// s.pop(); - /// ``` - } - try: { - /// ``` - /// use stack_collections::StackString; - /// - /// let mut s = StackString::<8>::new(); - /// assert!(s.try_pop().is_none()); - /// - /// s.push_str("hi"); - /// assert_eq!(s.try_pop(), Some('i')); - /// assert_eq!(s.try_pop(), Some('h')); - /// assert_eq!(s.try_pop(), None); - /// ``` - } - } - } - - /// Writes `c` to `dst` - #[inline(always)] - const unsafe fn write_char_to_ptr(&mut self, dst: *mut u8, c: char, c_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; - - let code = c as u32; - - match c_len { - 1 => { - unsafe { - dst.write(code as u8); - }; - } - 2 => { - unsafe { - dst.write(TAG_TWO_B | ((code >> 6) as u8)); - dst.add(1).write(TAG_CONT | ((code & 0x3F) as u8)); - }; - } - 3 => { - unsafe { - dst.write(TAG_THREE_B | ((code >> 12) as u8)); - dst.add(1).write(TAG_CONT | (((code >> 6) & 0x3F) as u8)); - dst.add(2).write(TAG_CONT | ((code & 0x3F) as u8)); - }; - } - _ => { - unsafe { - dst.write(TAG_FOUR_B | ((code >> 18) as u8)); - dst.add(1).write(TAG_CONT | (((code >> 12) & 0x3F) as u8)); - dst.add(2).write(TAG_CONT | (((code >> 6) & 0x3F) as u8)); - dst.add(3).write(TAG_CONT | ((code & 0x3F) as u8)); - }; - } - } - } - - // push - define_variants! { - fn push(&mut self, c: char) -> (), - - normal_brief: "Appends a char", - try_brief: "Attempts to append a char", - unchecked_brief_suffix: "without bound checking", - checks: { - self.len() + c.len_utf8() > N => "buffer capacity exceeded", - }, - prefixes: { - normal: {pub const}, - unchecked: {pub const}, - try: {pub const}, - }, - unchecked_fn: push_unchecked, - try_fn: try_push, - - body: { - let n = c.len_utf8(); - unsafe { - let dst = self.buf.as_mut_ptr().add(self.len).cast::(); - self.write_char_to_ptr(dst, c, n); - } - self.len += n; - }, - - examples: { - normal: { - /// Basic usage: - /// - /// ``` - /// use stack_collections::StackString; - /// - /// let mut s = StackString::<16>::new(); - /// s.push('a'); - /// s.push('😀'); - /// s.push('z'); - /// assert_eq!(s.as_str(), "a😀z"); - /// assert_eq!(s.len(), 6); - /// ``` - /// - /// A panic upon overflow: - /// ```should_panic - /// use stack_collections::StackString; - /// - /// let mut s = StackString::<1>::new(); - /// s.push('a'); - /// - /// // this will panic at runtime - /// s.push('b'); - /// ``` - } - try: { - /// ``` - /// use stack_collections::StackString; - /// - /// let mut s = StackString::<5>::new(); - /// assert!(s.try_push('a').is_some()); - /// assert_eq!(s.len(), 1); - /// assert!(s.try_push('😀').is_some()); - /// assert_eq!(s.len(), 5); - /// - /// assert!(s.try_push('x').is_none()); - /// assert_eq!(s.as_str(), "a😀"); - /// ``` - } - } - } - - // push_ascii - define_variants! { - #[cfg(feature = "nightly")] - #[cfg_attr(docsrs, doc(cfg(feature = "nightly")))] - fn push_ascii(&mut self, ascii: Char) -> (), - - normal_brief: "Appends an ASCII character", - try_brief: "Attempts to push an ASCII character", - unchecked_brief_suffix: "without bound checking", - checks: { - self.is_full() => "buffer capacity exceeded", - }, - prefixes: { - normal: {pub const}, - unchecked: {pub const}, - try: {pub const}, - }, - unchecked_fn: push_ascii_unchecked, - try_fn: try_push_ascii, - body: { - unsafe { - self.buf - .as_mut_ptr() - .add(self.len) - .cast::() - .write(ascii as u8); - }; - self.len += 1; - }, - examples: { - normal: { - /// Basic usage: - /// - /// ``` - /// use stack_collections::StackString; - /// use core::ascii::Char; - /// - /// let mut s = StackString::<16>::new(); - /// s.push_ascii(Char::SmallA); - /// s.push_ascii(Char::SmallZ); - /// assert_eq!(s.as_str(), "az"); - /// assert_eq!(s.len(), 2); - /// ``` - /// - /// A panic upon overflow: - /// - /// ```should_panic - /// use stack_collections::StackString; - /// use core::ascii::Char; - /// - /// let mut s = StackString::<1>::new(); - /// s.push_ascii(Char::SmallA); - /// - /// // this will panic at runtime - /// s.push_ascii(Char::SmallB); - /// ``` - } - try: { - /// ``` - /// use stack_collections::StackString; - /// use core::ascii::Char; - /// - /// let mut s = StackString::<3>::new(); - /// assert!(s.try_push_ascii(Char::SmallA).is_some()); - /// assert_eq!(s.len(), 1); - /// - /// assert!(s.try_push_ascii(Char::SmallB).is_some()); - /// assert_eq!(s.len(), 2); - /// - /// assert!(s.try_push_ascii(Char::SmallX).is_none()); - /// assert_eq!(s.as_str(), "ab"); - /// ``` - } - } - } - - // push_str - define_variants! { - fn push_str(&mut self, s: &str) -> (), - - normal_brief: "Appends a `&str`", - try_brief: "Attempts to append a `&str`", - unchecked_brief_suffix: "without bound checking", - checks: { - self.len + s.len() > N => "buffer capacity exceeded", - }, - prefixes: { - normal: {pub const}, - unchecked: {pub const}, - try: {pub const}, - }, - unchecked_fn: push_str_unchecked, - try_fn: try_push_str, - body: { - let bytes = s.as_bytes(); - unsafe { - let dst = self.buf.as_mut_ptr().add(self.len).cast::(); - ptr::copy_nonoverlapping(bytes.as_ptr(), dst, bytes.len()) - }; - self.len += bytes.len(); - }, - examples: { - normal: { - /// Basic usage: - /// - /// ``` - /// use stack_collections::StackString; - /// - /// let mut s = StackString::<32>::new(); - /// s.push_str("hello"); - /// assert_eq!(s.as_str(), "hello"); - /// assert_eq!(s.len(), 5); - /// - /// s.push_str(" world"); - /// assert_eq!(s.as_str(), "hello world"); - /// assert_eq!(s.len(), 11); - /// - /// s.push_str(""); - /// assert_eq!(s.as_str(), "hello world"); - /// assert_eq!(s.len(), 11); - /// ``` - /// - /// A panic upon overflow: - /// - /// ```should_panic - /// use stack_collections::StackString; - /// - /// let mut s = StackString::<4>::new(); - /// - /// // this will panic at runtime - /// s.push_str("hello"); - /// ``` - } - try: { - /// ``` - /// use stack_collections::StackString; - /// - /// let mut s = StackString::<8>::new(); - /// assert!(s.try_push_str("hello").is_some()); - /// assert_eq!(s.as_str(), "hello"); - /// - /// assert!(s.try_push_str("world").is_none()); - /// assert_eq!(s.as_str(), "hello"); - /// - /// assert!(s.try_push_str("!!!").is_some()); - /// assert_eq!(s.as_str(), "hello!!!"); - /// ``` - } - } - } - - // insert - define_variants! { - fn insert(&mut self, index: usize, c: char) -> (), - - normal_brief: "Inserts a `char` at `index`, shifting all elements after it", - try_brief: "Attempts to insert a `char` at `index`, shifting all elements after it", - unchecked_brief_suffix: "without bound or capacity checking", - checks: { - index > self.len => "index out of bounds", - self.len + c.len_utf8() > N => "buffer capacity exceeded", - }, - prefixes: { - normal: {pub const}, - unchecked: {pub const}, - try: {pub const}, - }, - unchecked_fn: insert_unchecked, - try_fn: try_insert, - body: { - let c_len = c.len_utf8(); - unsafe { - let dst = self.buf.as_mut_ptr().add(index).cast::(); - ptr::copy(dst, dst.add(c_len), self.len - index); - self.write_char_to_ptr(dst, c, c_len); - }; - - self.len += c_len; - }, - examples: { - normal: { - /// Basic usage: - /// - /// ``` - /// use stack_collections::StackString; - /// - /// let mut s = StackString::<8>::new(); - /// s.push('a'); - /// s.push('c'); - /// assert_eq!(s.as_str(), "ac"); - /// - /// s.insert(1, 'b'); - /// assert_eq!(s.as_str(), "abc"); - /// assert_eq!(s.len(), 3); - /// - /// s.insert(3, '7'); - /// assert_eq!(s.as_str(), "abc7"); - /// ``` - /// - /// A panic if the index is out of bounds: - /// - /// ```should_panic - /// use stack_collections::StackString; - /// - /// let mut s = StackString::<8>::new(); - /// s.push('a'); - /// s.push('c'); - /// assert_eq!(s.len(), 2); - /// - /// // this will panic at runtime - /// s.insert(3, 'b'); - /// ``` - /// - /// A panic upon overflow: - /// - /// ```should_panic - /// use stack_collections::StackString; - /// - /// let mut s = StackString::<2>::new(); - /// s.push('a'); - /// s.push('c'); - /// assert_eq!(s.len(), 2); - /// - /// // this will panic at runtime - /// s.insert(1, 'b'); - /// ``` - } - try: { - /// ``` - /// use stack_collections::StackString; - /// - /// let mut s = StackString::<8>::new(); - /// - /// s.push_str("abcd"); - /// assert_eq!(s.as_str(), "abcd"); - /// - /// assert!(s.try_insert(1, 'e').is_some()); - /// assert_eq!(s.as_str(), "aebcd"); - /// - /// // index out of bounds - /// assert!(s.try_insert(10, 'x').is_none()); - /// - /// // would exceed capacity - /// s.push_str("!!!"); - /// assert!(s.try_insert(0, 'x').is_none()); - /// ``` - } - } - } - - // insert_ascii - define_variants! { - #[cfg(feature = "nightly")] - #[cfg_attr(docsrs, doc(cfg(feature = "nightly")))] - fn insert_ascii(&mut self, index: usize, ascii: Char) -> (), - - normal_brief: "Inserts an ASCII character at `index`, shifting all elements after it", - try_brief: "Attempts to insert an ASCII character at `index`, shifting all elements after it", - unchecked_brief_suffix: "without bound or capacity checking", - checks: { - index > self.len => "index out of bounds", - self.is_full() => "buffer capacity exceeded", - }, - prefixes: { - normal: {pub const}, - unchecked: {pub const}, - try: {pub const}, - }, - unchecked_fn: insert_ascii_unchecked, - try_fn: try_insert_ascii, - body: { - unsafe { - let dst = self.buf.as_mut_ptr().add(index).cast::(); - ptr::copy(dst, dst.add(1), self.len - index); - dst.write(ascii as u8); - } - self.len += 1; - }, - examples: { - normal: { - /// Basic usage: - /// - /// ``` - /// use stack_collections::StackString; - /// use core::ascii::Char; - /// - /// let mut s = StackString::<8>::new(); - /// s.push_ascii(Char::SmallA); - /// s.push_ascii(Char::SmallC); - /// assert_eq!(s.as_str(), "ac"); - /// - /// s.insert_ascii(1, Char::SmallB); - /// assert_eq!(s.as_str(), "abc"); - /// ``` - /// - /// A panic if the index is out of bounds: - /// - /// ```should_panic - /// use stack_collections::StackString; - /// use core::ascii::Char; - /// - /// let mut s = StackString::<8>::new(); - /// s.push_ascii(Char::SmallA); - /// assert_eq!(s.len(), 1); - /// - /// // this will panic at runtime - /// s.insert_ascii(2, Char::SmallB); - /// ``` - /// - /// A panic upon overflow: - /// - /// ```should_panic - /// use stack_collections::StackString; - /// use core::ascii::Char; - /// - /// let mut s = StackString::<1>::new(); - /// s.push_ascii(Char::SmallA); - /// - /// // this will panic at runtime - /// s.insert_ascii(0, Char::SmallB); - /// ``` - } - try: { - /// ``` - /// use stack_collections::StackString; - /// use core::ascii::Char; - /// - /// let mut s = StackString::<3>::new(); - /// assert!(s.try_insert_ascii(0, Char::SmallA).is_some()); - /// assert_eq!(s.as_str(), "a"); - /// - /// assert!(s.try_insert_ascii(1, Char::SmallC).is_some()); - /// assert_eq!(s.as_str(), "ac"); - /// - /// // index out of bounds - /// assert!(s.try_insert_ascii(10, Char::SmallX).is_none()); - /// - /// assert!(s.try_insert_ascii(1, Char::SmallB).is_some()); - /// assert_eq!(s.as_str(), "abc"); - /// - /// // String is full - /// assert!(s.try_insert_ascii(0, Char::SmallX).is_none()); - /// assert_eq!(s.as_str(), "abc"); - /// ``` - } - } - } - - // insert_str - define_variants! { - fn insert_str(&mut self, index: usize, s: &str) -> (), - - normal_brief: "Inserts a `&str` at `index`, shifting all elements after it", - try_brief: "Attempts to insert a `&str` at `index`, shifting all elements after it", - unchecked_brief_suffix: "without bound or capacity checking", - checks: { - index > self.len => "index out of bounds", - self.len + s.len() > N => "buffer capacity exceeded", - }, - prefixes: { - normal: {pub const}, - unchecked: {pub const}, - try: {pub const}, - }, - unchecked_fn: insert_str_unchecked, - try_fn: try_insert_str, - body: { - let bytes = s.as_bytes(); - unsafe { - let dst = self.buf.as_mut_ptr().add(index).cast::(); - ptr::copy(dst, dst.add(bytes.len()), self.len - index); - ptr::copy_nonoverlapping(bytes.as_ptr(), dst, bytes.len()); - } - self.len += s.len(); - }, - examples: { - normal: { - /// Basic usage: - /// - /// ``` - /// use stack_collections::StackString; - /// - /// let mut s = StackString::<16>::new(); - /// s.push_str("123"); - /// assert_eq!(s.as_str(), "123"); - /// - /// s.insert_str(1, "ABC"); - /// assert_eq!(s.as_str(), "1ABC23"); - /// - /// s.insert_str(5, "DEF"); - /// assert_eq!(s.as_str(), "1ABC2DEF3"); - /// ``` - /// - /// A panic if the index is out of bounds: - /// - /// ```should_panic - /// use stack_collections::StackString; - /// - /// let mut s = StackString::<8>::new(); - /// s.push_str("hello"); - /// assert_eq!(s.len(), 5); - /// - /// // this will panic at runtime - /// s.insert_str(10, "x"); - /// ``` - /// - /// A panic upon overflow: - /// - /// ```should_panic - /// use stack_collections::StackString; - /// - /// let mut s = StackString::<8>::new(); - /// s.push_str("hello"); - /// assert_eq!(s.len(), 5); - /// - /// // this will panic at runtime - /// s.insert_str(0, "world"); - /// ``` - } - try: { - /// ``` - /// use stack_collections::StackString; - /// - /// let mut s = StackString::<12>::new(); - /// s.push_str("hd"); - /// assert_eq!(s.as_str(), "hd"); - /// - /// assert!(s.try_insert_str(1, "el").is_some()); - /// assert_eq!(s.as_str(), "held"); - /// - /// assert!(s.try_insert_str(4, " world").is_some()); - /// assert_eq!(s.as_str(), "held world"); - /// - /// // would exceed capacity - /// assert!(s.try_insert_str(0, "xxx").is_none()); - /// assert_eq!(s.as_str(), "held world"); - /// - /// // index out of bounds - /// assert!(s.try_insert_str(20, "x").is_none()); - /// ``` - } - } - } - - /// Truncates the string to the specified byte length. - /// - /// # Panics - /// - /// Panics if `new_len` does not lie on a UTF-8 character boundary. - /// - /// # Examples - /// - /// Basic usage: - /// - /// ``` - /// use stack_collections::StackString; - /// - /// let mut s = StackString::<16>::new(); - /// s.push_str("hello"); - /// s.truncate(3); - /// assert_eq!(s.as_str(), "hel"); - /// assert_eq!(s.len(), 3); - /// - /// s.truncate(10); - /// assert_eq!(s.as_str(), "hel"); - /// ``` - /// - /// A panic upon invalid UTF-8 character boundary: - /// - /// ```should_panic - /// use stack_collections::StackString; - /// - /// let mut s = StackString::<16>::new(); - /// s.push_str("hello😀"); - /// - /// // this will panic at runtime - /// s.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 s = StackString::<8>::new(); - /// s.push_str("hello"); - /// assert_eq!(s.as_bytes(), b"hello"); - /// ``` - #[must_use] - #[inline] - pub const fn as_bytes(&self) -> &[u8] { - unsafe { slice::from_raw_parts(self.buf.as_ptr().cast::(), self.len) } - } - - /// Borrow the content as `&str`. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackString; - /// - /// let mut s = StackString::<8>::new(); - /// s.push_str("test"); - /// assert_eq!(s.as_str(), "test"); - /// ``` - #[must_use] - #[inline] - pub const fn as_str(&self) -> &str { - unsafe { str::from_utf8_unchecked(self.as_bytes()) } - } - - /// Borrow the content as a mutable `str`. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackString; - /// - /// let mut s = StackString::<16>::new(); - /// s.push_str("hello"); - /// - /// let mutable_str = s.as_mut_str(); - /// mutable_str.make_ascii_uppercase(); - /// - /// assert_eq!(s.as_str(), "HELLO"); - /// ``` - #[must_use] - #[inline] - pub const fn as_mut_str(&mut self) -> &mut str { - unsafe { - str::from_utf8_unchecked_mut(slice::from_raw_parts_mut( - self.buf.as_mut_ptr().cast::(), - self.len, - )) - } - } - - /// Clears the string by setting `len = 0`. - /// - /// The underlying memory is not zeroed. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackString; - /// - /// let mut s = StackString::<8>::new(); - /// s.push_str("hello"); - /// assert!(!s.is_empty()); - /// - /// s.clear(); - /// assert!(s.is_empty()); - /// assert_eq!(s.len(), 0); - /// assert_eq!(s.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 s = StackString::<16>::new(); - /// assert_eq!(s.len(), 0); - /// s.push_str("hello"); - /// assert_eq!(s.len(), 5); - /// ``` - #[must_use] - #[inline] - pub const fn len(&self) -> usize { - self.len - } - - /// Returns the capacity in bytes. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackString; - /// - /// let s = StackString::<32>::new(); - /// assert_eq!(s.capacity(), 32); - /// ``` - #[inline(always)] - #[must_use] - pub const fn capacity(&self) -> usize { - N - } - - /// Returns the remaining capacity in bytes. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackString; - /// - /// let mut s = StackString::<10>::new(); - /// assert_eq!(s.remaining_capacity(), 10); - /// - /// s.push_str("hello"); - /// assert_eq!(s.remaining_capacity(), 5); - /// - /// s.push_str("world"); - /// assert_eq!(s.remaining_capacity(), 0); - /// assert!(s.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 s = StackString::<8>::new(); - /// assert!(s.is_empty()); - /// s.push('a'); - /// assert!(!s.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 s = StackString::<5>::new(); - /// assert!(!s.is_full()); - /// s.push_str("hello"); - /// assert!(s.is_full()); - /// ``` - #[must_use] - #[inline] - pub const fn is_full(&self) -> bool { - self.len >= N - } -} - -impl Default for StackString { - /// Returns a new empty `StackString`. - /// - /// This is equivalent to [`Self::new`]. - #[inline] - fn default() -> Self { - Self::new() - } -} - -impl Deref for StackString { - type Target = str; - - /// Returns a string slice of the `StackString`. - /// - /// This is equivalent to [`Self::as_str`]. - #[inline] - fn deref(&self) -> &Self::Target { - self.as_str() - } -} - -impl DerefMut for StackString { - /// Returns a mutable string slice of the `StackString`. - /// - /// This is equivalent to [`Self::as_mut_str`]. - #[inline] - fn deref_mut(&mut self) -> &mut Self::Target { - self.as_mut_str() - } -} - -impl AsRef for StackString { - /// Returns a string slice to the `StackString`. - /// - /// This is equivalent to [`Self::as_str`]. - #[inline] - fn as_ref(&self) -> &str { - self.as_str() - } -} - -impl AsMut for StackString { - /// Borrow the content as a mutable `str`. - /// - /// This is equivalent to [`Self::as_mut_str`]. - #[inline] - fn as_mut(&mut self) -> &mut str { - self.as_mut_str() - } -} - -impl fmt::Debug for StackString { - /// Formats the `StackString` with quotes, like a normal string slice. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackString; - /// - /// let s: StackString<16> = "test".try_into().unwrap(); - /// assert_eq!(format!("{:?}", s), "\"test\""); - /// ``` - #[inline] - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(self.as_str(), f) - } -} - -impl fmt::Display for StackString { - /// Formats the `StackString` without quotes, like a normal string slice. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackString; - /// - /// let s: StackString<16> = "test".try_into().unwrap(); - /// assert_eq!(format!("{}", s), "test"); - /// ``` - #[inline] - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(self.as_str(), f) - } -} - -impl PartialEq for StackString { - /// Compares two `StackString`s for equality. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackString; - /// - /// let s1: StackString<16> = "test".try_into().unwrap(); - /// let s2: StackString<16> = "test".try_into().unwrap(); - /// let s3: StackString<16> = "other".try_into().unwrap(); - /// - /// assert_eq!(s1, s2); - /// assert_ne!(s1, s3); - /// assert_eq!(s1, "test"); - /// assert_eq!(&s1, &"test"); - /// ``` - #[inline] - fn eq(&self, other: &Self) -> bool { - self.as_str() == other.as_str() - } -} - -impl PartialEq for StackString { - #[inline] - fn eq(&self, other: &str) -> bool { - self.as_str() == other - } -} - -impl PartialEq<&str> for StackString { - #[inline] - fn eq(&self, other: &&str) -> bool { - self.as_str() == *other - } -} - -impl Eq for StackString {} - -impl PartialOrd for StackString { - /// Performs lexicographic ordering on `StackString`s, same as `str`. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackString; - /// - /// let s1: StackString<8> = "abc".try_into().unwrap(); - /// let s2: StackString<8> = "abd".try_into().unwrap(); - /// let s3: StackString<8> = "abc".try_into().unwrap(); - /// - /// assert!(s1 < s2); - /// assert!(s2 > s1); - /// assert!(s1 <= s3); - /// assert!(s1 >= s3); - /// ``` - #[inline] - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for StackString { - #[inline] - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - self.as_str().cmp(other.as_str()) - } -} - -impl Hash for StackString { - /// Hashes the contents of the string. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackString; - /// use core::hash::{Hash, Hasher}; - /// - /// // A simple Hasher for testing purposes - /// struct MyTestHasher { - /// hash: u64, - /// } - /// - /// impl Hasher for MyTestHasher { - /// fn write(&mut self, bytes: &[u8]) { - /// for &byte in bytes { - /// self.hash = self.hash.wrapping_add(byte as u64); - /// } - /// } - /// fn finish(&self) -> u64 { - /// self.hash - /// } - /// } - /// - /// let s1: StackString<16> = "key".try_into().unwrap(); - /// let s2: StackString<16> = "key".try_into().unwrap(); - /// - /// let mut hasher1 = MyTestHasher { hash: 0 }; - /// let mut hasher2 = MyTestHasher { hash: 0 }; - /// - /// s1.hash(&mut hasher1); - /// s2.hash(&mut hasher2); - /// - /// assert_eq!(hasher1.finish(), hasher2.finish()); - /// ``` - #[inline] - fn hash(&self, state: &mut H) { - self.as_str().hash(state); - } -} - -impl TryFrom<&str> for StackString { - type Error = &'static str; - - /// Attempts to create a `StackString` from a string slice. - /// - /// Returns an error if the input string exceeds the buffer capacity. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackString; - /// use core::convert::TryFrom; - /// - /// let s: StackString<16> = "hello".try_into().unwrap(); - /// assert_eq!(s.as_str(), "hello"); - /// - /// // Fails if the string is too long for the buffer - /// let result: Result, _> = "hello".try_into(); - /// assert!(result.is_err()); - /// ``` - #[inline] - fn try_from(s: &str) -> Result { - if s.len() > N { - return Err("buffer capacity exceeded"); - } - let mut out = StackString::new(); - unsafe { out.push_str_unchecked(s) }; - Ok(out) - } -} - -#[cfg(feature = "std")] -#[cfg_attr(docsrs, doc(cfg(feature = "std")))] -impl From> for String { - /// Converts a `StackString` into a `String`. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackString; - /// - /// let s: StackString<16> = "hello".try_into().unwrap(); - /// let string: String = s.into(); - /// - /// assert_eq!(string, "hello"); - /// ``` - fn from(value: StackString) -> Self { - value.as_str().to_owned() - } -} - -impl Clone for StackString { - /// Creates a copy of the `StackString`. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackString; - /// - /// let s1: StackString<16> = "hello".try_into().unwrap(); - /// let s2 = s1.clone(); - /// - /// assert_eq!(s1, s2); - /// assert_eq!(s1.as_str(), s2.as_str()); - /// ``` - #[inline] - fn clone(&self) -> Self { - let mut out = StackString::new(); - unsafe { out.push_str_unchecked(self.as_str()) }; - out - } -} - -#[cfg(feature = "std")] -#[cfg_attr(docsrs, doc(cfg(feature = "std")))] -impl Write for StackString { - /// Writes a buffer into the `StackString`. - /// - /// Returns the number of bytes written. Per the `Write` trait contract: - /// - Returns `Ok(0)` if the buffer is full or input is empty. - /// - Returns `Ok(n)` where `n <= buf.len()` for partial writes. - /// - Only returns `Err` if the input would create invalid UTF-8. - /// - /// # Examples - /// - /// Basic usage: - /// - /// ``` - /// use stack_collections::StackString; - /// use std::io::Write; - /// - /// let mut s = StackString::<16>::new(); - /// assert_eq!(s.write(b"Hello").unwrap(), 5); - /// assert_eq!(s.write(b" world!").unwrap(), 7); - /// assert_eq!(s.as_str(), "Hello world!"); - /// ``` - /// - /// Partial writes: - /// - /// ``` - /// use stack_collections::StackString; - /// use std::io::Write; - /// - /// let mut s = StackString::<8>::new(); - /// s.push_str("1234"); - /// - /// let written = s.write(b"567890").unwrap(); - /// assert_eq!(written, 4); - /// assert_eq!(s.as_str(), "12345678"); - /// ``` - fn write(&mut self, buf: &[u8]) -> io::Result { - let available = N - self.len; - if available == 0 || buf.is_empty() { - return Ok(0); - } - - let mut to_write = buf.len().min(available); - - // Walk back to avoid splitting UTF-8 sequences - while to_write > 0 && (buf[to_write - 1] & 0b1100_0000) == 0b1000_0000 { - to_write -= 1; - } - - if to_write == 0 { - return Ok(0); - } - - // Validate the bytes we're writing - str::from_utf8(&buf[..to_write]) - .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "invalid UTF-8"))?; - - unsafe { - let dst = self.buf.as_mut_ptr().add(self.len).cast::(); - ptr::copy_nonoverlapping(buf.as_ptr(), dst, to_write); - self.len += to_write; - } - - Ok(to_write) - } - - #[inline] - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -impl fmt::Write for StackString { - /// Writes a string slice into the `StackString`. - /// - /// Returns an error if the string would exceed capacity. - /// - /// # Examples - /// - /// Basic usage: - /// - /// ``` - /// use stack_collections::StackString; - /// use core::fmt::Write; - /// - /// let mut s = StackString::<32>::new(); - /// write!(s, "Hello {}", "world").unwrap(); - /// assert_eq!(s.as_str(), "Hello world"); - /// ``` - /// - /// Error on overflow: - /// - /// ``` - /// use stack_collections::StackString; - /// use core::fmt::Write; - /// - /// let mut s = StackString::<5>::new(); - /// assert!(write!(s, "hello world").is_err()); - /// ``` - fn write_str(&mut self, s: &str) -> fmt::Result { - self.try_push_str(s).ok_or(fmt::Error) - } - - fn write_char(&mut self, c: char) -> fmt::Result { - self.try_push(c).ok_or(fmt::Error) - } -} - -/// A stack-allocated vector with a fixed capacity. -pub struct StackVec { - /// The underlying storage for elements, allocated on the stack. - data: [MaybeUninit; CAP], - - /// The current number of initialized elements in the vector. - len: usize, -} - -impl StackVec { - /// Creates a new `StackVec`. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackVec; - /// - /// let v = StackVec::::new(); - /// assert_eq!(v.len(), 0); - /// assert_eq!(v.capacity(), 8); - /// assert!(v.is_empty()); - /// assert!(!v.is_full()); - /// ``` - #[inline] - pub const fn new() -> Self { - Self { - data: unsafe { MaybeUninit::uninit().assume_init() }, - len: 0, - } - } - - /// Returns a raw pointer to the vector's buffer. - /// - /// The pointer is valid for reads of `self.len()` elements. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackVec; - /// - /// let mut v = StackVec::::new(); - /// v.push(1); - /// v.push(2); - /// v.push(3); - /// - /// let ptr = v.as_ptr(); - /// unsafe { - /// assert_eq!(*ptr, 1); - /// assert_eq!(*ptr.add(1), 2); - /// assert_eq!(*ptr.add(2), 3); - /// } - /// ``` - #[must_use] - #[inline] - pub const fn as_ptr(&self) -> *const T { - self.data.as_ptr() as *const T - } - - /// Returns a mutable raw pointer to the vector's buffer. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackVec; - /// - /// let mut v = StackVec::::new(); - /// v.push(1); - /// v.push(2); - /// v.push(3); - /// - /// unsafe { - /// let ptr = v.as_mut_ptr(); - /// *ptr = 10; - /// *ptr.add(2) = 30; - /// } - /// assert_eq!(v.as_slice(), &[10, 2, 30]); - /// ``` - #[must_use] - #[inline] - pub const fn as_mut_ptr(&mut self) -> *mut T { - self.data.as_mut_ptr() as *mut T - } - - // push - define_variants! { - fn push(&mut self, value: T) -> (), - - normal_brief: "Appends a value", - try_brief: "Attempts to append a value", - unchecked_brief_suffix: "without bound checking", - checks: { - self.is_full() => "buffer capacity exceeded", - }, - prefixes: { - normal: {pub const}, - unchecked: {pub const}, - try: {pub}, - }, - unchecked_fn: push_unchecked, - try_fn: try_push, - - body: { - unsafe { - self.data - .as_mut_ptr() - .add(self.len) - .write(MaybeUninit::new(value)); - } - self.len += 1; - }, - - examples: { - normal: { - /// Basic usage: - /// - /// ``` - /// use stack_collections::StackVec; - /// - /// let mut v = StackVec::::new(); - /// v.push(1); - /// v.push(2); - /// v.push(3); - /// - /// assert_eq!(v.len(), 3); - /// assert_eq!(v[0], 1); - /// assert_eq!(v[1], 2); - /// assert_eq!(v[2], 3); - /// ``` - /// - /// A panic upon overflow: - /// - /// ```should_panic - /// use stack_collections::StackVec; - /// - /// let mut v = StackVec::::new(); - /// v.push(1); - /// v.push(2); - /// - /// // this will panic at runtime - /// v.push(3); - /// ``` - } - try: { - /// ``` - /// use stack_collections::StackVec; - /// - /// let mut v = StackVec::::new(); - /// assert!(v.try_push(1).is_some()); - /// assert!(v.try_push(2).is_some()); - /// assert!(v.try_push(3).is_none()); - /// - /// assert_eq!(v.len(), 2); - /// assert_eq!(v[0], 1); - /// assert_eq!(v[1], 2); - /// ``` - } - } - } - - // insert - define_variants! { - fn insert(&mut self, index: usize, element: T) -> (), - - 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", - checks: { - index > self.len() => "index out of bounds", - self.is_full() => "buffer capacity exceeded", - }, - prefixes: { - normal: {pub const}, - unchecked: {pub const}, - try: {pub}, - }, - - unchecked_fn: insert_unchecked, - try_fn: try_insert, - - body: { - unsafe { - let p = self.data.as_mut_ptr().add(index) as *mut T; - ptr::copy(p, p.add(1), self.len - index); - ptr::write(p, element); - } - self.len += 1; - }, - - examples: { - normal: { - /// Basic usage: - /// - /// ``` - /// use stack_collections::StackVec; - /// - /// let mut v = StackVec::::new(); - /// v.push(1); - /// v.push(3); - /// assert_eq!(v.len(), 2); - /// - /// v.insert(1, 2); - /// assert_eq!(v.len(), 3); - /// assert_eq!(v[0], 1); - /// assert_eq!(v[1], 2); - /// assert_eq!(v[2], 3); - /// ``` - /// - /// A panic if the index is out of bounds: - /// - /// ```should_panic - /// use stack_collections::StackVec; - /// - /// let mut v = StackVec::::new(); - /// v.push(40); - /// assert_eq!(v.len(), 1); - /// - /// // this will panic at runtime - /// v.insert(2, 10); - /// ``` - /// - /// A panic upon overflow: - /// - /// ```should_panic - /// use stack_collections::StackVec; - /// - /// let mut v = StackVec::::new(); - /// v.push(40); - /// v.push(50); - /// assert!(v.is_full()); - /// - /// // this will panic at runtime - /// v.insert(1, 19); - /// ``` - } - try: { - /// ``` - /// use stack_collections::StackVec; - /// - /// let mut v = StackVec::::new(); - /// v.push(10); - /// v.push(20); - /// - /// assert_eq!(v.len(), 2); - /// - /// // index out of bounds - /// assert!(v.try_insert(3, 30).is_none()); - /// - /// assert!(v.try_insert(1, 30).is_some()); - /// assert_eq!(v.len(), 3); - /// - /// // vector is full - /// assert!(v.try_insert(3, 10).is_none()); - /// ``` - } - } - } - - // remove - define_variants! { - fn remove(&mut self, index: usize) -> T, - - 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", - checks: { - index >= self.len() => "index out of bounds", - }, - prefixes: { - normal: {pub const}, - unchecked: {pub const}, - try: {pub const}, - }, - unchecked_fn: remove_unchecked, - try_fn: try_remove, - - body: { - unsafe { - let p = self.data.as_mut_ptr().add(index) as *mut T; - let result = ptr::read(p); - ptr::copy(p.add(1), p, self.len - index - 1); - self.len -= 1; - result - } - }, - - examples: { - normal: { - /// Basic usage: - /// - /// ``` - /// use stack_collections::StackVec; - /// - /// let mut v = StackVec::::new(); - /// v.push(1); - /// v.push(2); - /// v.push(3); - /// - /// let removed = v.remove(1); - /// assert_eq!(removed, 2); - /// assert_eq!(v.len(), 2); - /// assert_eq!(v[0], 1); - /// assert_eq!(v[1], 3); - /// ``` - /// - /// A panic if the index is out of bounds: - /// - /// ```should_panic - /// use stack_collections::StackVec; - /// - /// let mut v = StackVec::::new(); - /// v.push(1); - /// - /// // this will panic at runtime - /// v.remove(1); - /// ``` - } - try: { - /// ``` - /// use stack_collections::StackVec; - /// - /// let mut v = StackVec::::new(); - /// v.push(10); - /// - /// assert_eq!(v.try_remove(0), Some(10)); - /// assert_eq!(v.try_remove(0), None); - /// ``` - } - } - } - - // pop - define_variants! { - fn pop(&mut self) -> T, - - normal_brief: "Removes and returns the last element", - try_brief: "Attempts to remove and return the last element", - unchecked_brief_suffix: "without bound checking", - checks: { - self.is_empty() => "vector is empty", - }, - prefixes: { - normal: {pub const}, - unchecked: {pub const}, - try: {pub const}, - }, - unchecked_fn: pop_unchecked, - try_fn: try_pop, - body: { - self.len -= 1; - unsafe { self.data.as_ptr().add(self.len).cast::().read() } - }, - examples: { - normal: { - /// Basic usage: - /// - /// ``` - /// use stack_collections::StackVec; - /// - /// let mut v = StackVec::::new(); - /// v.push(1); - /// v.push(2); - /// v.push(3); - /// - /// assert_eq!(v.pop(), 3); - /// assert_eq!(v.len(), 2); - /// assert_eq!(v.pop(), 2); - /// assert_eq!(v.pop(), 1); - /// assert!(v.is_empty()); - /// ``` - /// - /// A panic if the vector is empty: - /// - /// ```should_panic - /// use stack_collections::StackVec; - /// - /// let mut v = StackVec::::new(); - /// - /// // this will panic at runtime - /// v.pop(); - /// ``` - } - try: { - /// ``` - /// use stack_collections::StackVec; - /// - /// let mut v = StackVec::::new(); - /// assert_eq!(v.try_pop(), None); - /// - /// v.push(42); - /// assert_eq!(v.try_pop(), Some(42)); - /// assert_eq!(v.try_pop(), None); - /// ``` - } - } - } - - // swap_remove - define_variants! { - fn swap_remove(&mut self, index: usize) -> 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)", - unchecked_brief_suffix: "without bound checking", - checks: { - index >= self.len() => "index out of bounds", - }, - prefixes: { - normal: {pub const}, - unchecked: {pub const}, - try: {pub const}, - }, - unchecked_fn: swap_remove_unchecked, - try_fn: try_swap_remove, - - body: { - unsafe { - let p = self.data.as_mut_ptr().add(index) as *mut T; - let result = ptr::read(p); - self.len -= 1; - if index != self.len { - let last = self.data.as_ptr().add(self.len).cast::().read(); - ptr::write(p, last); - } - result - } - }, - - examples: { - normal: { - /// Basic usage: - /// - /// ``` - /// use stack_collections::StackVec; - /// - /// let mut v = StackVec::::new(); - /// v.push(1); - /// v.push(2); - /// v.push(3); - /// v.push(4); - /// - /// // remove middle element - /// let removed = v.swap_remove(1); - /// assert_eq!(removed, 2); - /// assert_eq!(v.len(), 3); - /// assert_eq!(v[0], 1); - /// assert_eq!(v[1], 4); - /// assert_eq!(v[2], 3); - /// - /// // remove last element - /// let removed_last = v.swap_remove(2); - /// assert_eq!(removed_last, 3); - /// assert_eq!(v.len(), 2); - /// assert_eq!(v[0], 1); - /// assert_eq!(v[1], 4); - /// ``` - /// - /// A panic if the index is out of bounds: - /// - /// ```should_panic - /// use stack_collections::StackVec; - /// - /// let mut v = StackVec::::new(); - /// v.push(10); - /// - /// // this will panic at runtime - /// v.swap_remove(1); - /// ``` - } - try: { - /// ``` - /// use stack_collections::StackVec; - /// - /// let mut v = StackVec::::new(); - /// v.extend_from_slice(&[1, 2, 3]); - /// - /// assert_eq!(v.try_swap_remove(1), Some(2)); - /// assert_eq!(v.as_slice(), &[1, 3]); - /// - /// assert_eq!(v.try_swap_remove(10), None); - /// ``` - } - } - } - - /// Retains only elements that satisfy the predicate. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackVec; - /// - /// let mut v = StackVec::::new(); - /// v.extend_from_slice(&[1, 2, 3, 4, 5]); - /// - /// v.retain(|x| *x % 2 == 0); - /// assert_eq!(v.as_slice(), &[2, 4]); - /// - /// v.retain(|_| true); - /// assert_eq!(v.as_slice(), &[2, 4]); - /// - /// v.retain(|_| false); - /// assert!(v.is_empty()); - /// ``` - #[inline] - pub fn retain(&mut self, mut f: F) - where - F: FnMut(&mut T) -> bool, - { - let mut kept = 0; - for i in 0..self.len { - let ptr = unsafe { self.as_mut_ptr().add(i) }; - let elem = unsafe { &mut *ptr }; - if f(elem) { - if kept != i { - unsafe { - let dst = self.as_mut_ptr().add(kept); - ptr::copy_nonoverlapping(ptr, dst, 1); - } - } - kept += 1; - } else { - unsafe { ptr::drop_in_place(elem) }; - } - } - - self.len = kept; - } - - /// Returns a reference to the element at `index`, without bounds checking. - /// - /// # Safety - /// - /// The caller must ensure that `index < self.len()`. - /// - #[must_use] - #[inline] - pub const unsafe fn get_unchecked(&self, index: usize) -> &T { - unsafe { (&*self.data.as_ptr().add(index)).assume_init_ref() } - } - - /// Returns a mutable reference to the element at `index`, without bounds checking. - /// - /// # Safety - /// - /// The caller must ensure that `index < self.len()`. - /// - #[must_use] - #[inline] - pub const unsafe fn get_unchecked_mut(&mut self, index: usize) -> &mut T { - unsafe { (&mut *self.data.as_mut_ptr().add(index)).assume_init_mut() } - } - - /// Returns a reference to the element at `index`. - /// - /// # Panics - /// - /// Panics if `index >= self.len()`. - /// - /// # Examples - /// - /// Basic usage: - /// - /// ``` - /// use stack_collections::StackVec; - /// - /// let mut v = StackVec::::new(); - /// v.extend_from_slice(&[10, 20, 30]); - /// - /// assert_eq!(*v.index(0), 10); - /// assert_eq!(*v.index(1), 20); - /// assert_eq!(*v.index(2), 30); - /// ``` - /// - /// A panic if the index is out of bounds: - /// - /// ```should_panic - /// use stack_collections::StackVec; - /// - /// let mut v = StackVec::::new(); - /// - /// // this will panic at runtime - /// v.index(0); - /// ``` - #[must_use] - #[inline] - pub const fn index(&self, index: usize) -> &T { - self.get(index).expect("index out of bounds") - } - - /// Returns a mutable reference to the element at `index`. - /// - /// # Panics - /// - /// Panics if `index >= self.len()`. - /// - /// # Examples - /// - /// Basic usage: - /// - /// ``` - /// use stack_collections::StackVec; - /// - /// let mut v = StackVec::::new(); - /// v.extend_from_slice(&[10, 20, 30]); - /// - /// *v.index_mut(1) = 42; - /// assert_eq!(v[1], 42); - /// ``` - /// - /// A panic if the index is out of bounds: - /// - /// ```should_panic - /// use stack_collections::StackVec; - /// - /// let mut v = StackVec::::new(); - /// - /// // this will panic at runtime - /// v.index_mut(0); - /// ``` - #[must_use] - #[inline] - pub const fn index_mut(&mut self, index: usize) -> &mut T { - self.get_mut(index).expect("index out of bounds") - } - - /// Attempts to return a reference to the element at `index`. - /// - /// Returns `None` if `index >= self.len()`. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackVec; - /// - /// let mut v = StackVec::::new(); - /// v.push(10); - /// v.push(20); - /// v.push(30); - /// - /// assert_eq!(v.get(0), Some(&10)); - /// assert_eq!(v.get(1), Some(&20)); - /// assert_eq!(v.get(2), Some(&30)); - /// assert_eq!(v.get(3), None); - /// ``` - #[must_use] - #[inline] - pub const fn get(&self, index: usize) -> Option<&T> { - if index >= self.len { - None - } else { - Some(unsafe { (&*self.data.as_ptr().add(index)).assume_init_ref() }) - } - } - - /// Attempts to return a mutable reference to the element at `index`. - /// - /// Returns `None` if `index >= self.len()`. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackVec; - /// - /// let mut v = StackVec::::new(); - /// v.push(10); - /// v.push(20); - /// v.push(30); - /// - /// if let Some(val) = v.get_mut(1) { - /// *val = 42; - /// } - /// assert_eq!(v[1], 42); - /// - /// assert_eq!(v.get_mut(10), None); - /// ``` - #[must_use] - #[inline] - pub const fn get_mut(&mut self, index: usize) -> Option<&mut T> { - if index >= self.len { - None - } else { - Some(unsafe { (&mut *self.data.as_mut_ptr().add(index)).assume_init_mut() }) - } - } - - /// Returns the contents as a slice. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackVec; - /// - /// let mut v = StackVec::::new(); - /// v.extend_from_slice(&[1, 2, 3]); - /// assert_eq!(v.as_slice(), &[1, 2, 3]); - /// ``` - #[must_use] - #[inline] - pub const fn as_slice(&self) -> &[T] { - unsafe { slice::from_raw_parts(self.data.as_ptr() as *const T, self.len) } - } - - /// Returns the contents as a mutable slice. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackVec; - /// - /// let mut v = StackVec::::new(); - /// v.extend_from_slice(&[1, 2, 3]); - /// let slice = v.as_mut_slice(); - /// slice[1] = 42; - /// assert_eq!(v[1], 42); - /// ``` - #[must_use] - #[inline] - pub const fn as_mut_slice(&mut self) -> &mut [T] { - unsafe { slice::from_raw_parts_mut(self.data.as_mut_ptr() as *mut T, self.len) } - } - - /// Returns an iterator over the slice. - /// - /// The iterator yields all items from start to end. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackVec;; - /// - /// let mut v = StackVec::::new(); - /// v.push(1); - /// v.push(2); - /// v.push(3); - /// - /// let sum: i32 = v.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 v = StackVec::::new(); - /// v.push(1); - /// v.push(2); - /// v.push(3); - /// - /// for x in v.iter_mut() { - /// *x *= 2; - /// } - /// assert_eq!(v[0], 2); - /// assert_eq!(v[1], 4); - /// assert_eq!(v[2], 6); - /// ``` - #[inline] - pub fn iter_mut(&mut self) -> slice::IterMut<'_, T> { - self.as_mut_slice().iter_mut() - } - - define_variants! { - fn extend_from_slice(&mut self, slice: &[T]) -> (), - where_clause: { T: Clone } - - normal_brief: "Extends the vector from a slice", - try_brief: "Attempts to extend the vector from a slice", - unchecked_brief_suffix: "without capacity checking", - checks: { - 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 { - unsafe { - self.push_unchecked(item.clone()); - } - } - } else { - unsafe { - ptr::copy_nonoverlapping( - slice.as_ptr(), - self.data.as_mut_ptr().cast::().add(self.len), - slice.len(), - ); - } - self.len += slice.len(); - } - }, - examples: { - normal: { - /// Basic usage: - /// - /// ``` - /// use stack_collections::StackVec; - /// - /// let mut v = StackVec::::new(); - /// v.push(1); - /// v.extend_from_slice(&[2, 3, 4]); - /// - /// assert_eq!(v.len(), 4); - /// assert_eq!(v[0], 1); - /// assert_eq!(v[1], 2); - /// assert_eq!(v[2], 3); - /// assert_eq!(v[3], 4); - /// ``` - /// - /// A panic upon overflow: - /// - /// ```should_panic - /// use stack_collections::StackVec; - /// - /// let mut v = StackVec::::new(); - /// v.extend_from_slice(&[1, 2, 3, 4, 5]); - /// ``` - } - try: { - /// ``` - /// use stack_collections::StackVec; - /// - /// let mut v = StackVec::::new(); - /// v.push(1); - /// - /// assert!(v.try_extend_from_slice(&[2, 3]).is_some()); - /// assert_eq!(v.len(), 3); - /// - /// assert!(v.try_extend_from_slice(&[4, 5]).is_none()); - /// assert_eq!(v.len(), 3); - /// ``` - } - } - } - - /// 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::() { - unsafe { - for i in start..self.len { - self.data.get_unchecked_mut(i).assume_init_drop(); - } - } - } - } - - /// Truncates the vector to the specified length. - /// - /// Does nothing if `len >= self.len()`. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackVec; - /// - /// let mut v = StackVec::::new(); - /// v.push(1); - /// v.push(2); - /// v.push(3); - /// - /// v.truncate(2); - /// assert_eq!(v.len(), 2); - /// assert_eq!(v[0], 1); - /// assert_eq!(v[1], 2); - /// - /// v.truncate(10); - /// assert_eq!(v.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 v = StackVec::::new(); - /// v.push(1); - /// v.push(2); - /// - /// v.clear(); - /// assert_eq!(v.len(), 0); - /// assert!(v.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 v = StackVec::::new(); - /// assert_eq!(v.len(), 0); - /// v.push(1); - /// assert_eq!(v.len(), 1); - /// ``` - #[inline(always)] - #[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); - /// ``` - #[inline(always)] - #[must_use] - pub const fn capacity(&self) -> usize { - CAP - } - - /// Returns the remaining capacity. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackVec; - /// - /// let mut v = StackVec::::new(); - /// assert_eq!(v.remaining_capacity(), 5); - /// - /// v.push(1); - /// v.push(2); - /// assert_eq!(v.remaining_capacity(), 3); - /// - /// v.extend_from_slice(&[3, 4, 5]); - /// assert_eq!(v.remaining_capacity(), 0); - /// assert!(v.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 v = StackVec::::new(); - /// assert!(v.is_empty()); - /// v.push(1); - /// assert!(!v.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 v = StackVec::::new(); - /// assert!(!v.is_full()); - /// v.push(1); - /// v.push(2); - /// assert!(v.is_full()); - /// ``` - #[must_use] - #[inline] - pub const fn is_full(&self) -> bool { - self.len >= CAP - } -} - -impl Default for StackVec { - /// Returns a new empty `StackVec`. - /// - /// This is equivalent to [`Self::new`]. - #[inline] - fn default() -> Self { - Self::new() - } -} - -impl Drop for StackVec { - /// Drops the `StackVec`, cleaning up all initialized elements. - /// - /// This ensures that all elements stored in the vector are properly dropped - /// when the vector goes out of scope. - #[inline] - fn drop(&mut self) { - self.drop_elements(0); - } -} - -impl<'a, T, const CAP: usize> IntoIterator for &'a StackVec { - type Item = &'a T; - type IntoIter = slice::Iter<'a, T>; - - /// Converts the `StackVec` into an owning iterator. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackVec; - /// - /// let mut v = StackVec::::new(); - /// v.push(1); - /// v.push(2); - /// v.push(3); - /// - /// let collected: Vec<_> = v.into_iter().collect(); - /// assert_eq!(collected, vec![1, 2, 3]); - /// ``` - #[inline] - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} - -impl<'a, T, const CAP: usize> IntoIterator for &'a mut StackVec { - type Item = &'a mut T; - type IntoIter = slice::IterMut<'a, T>; - - /// Converts the `StackVec` into a mutable iterator. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackVec; - /// - /// let mut v = StackVec::::new(); - /// v.push(1); - /// v.push(2); - /// v.push(3); - /// - /// for x in &mut v { - /// *x *= 2; - /// } - /// - /// let collected: Vec<_> = v.iter().copied().collect(); - /// assert_eq!(collected, vec![2, 4, 6]); - /// ``` - #[inline] - fn into_iter(self) -> Self::IntoIter { - self.iter_mut() - } -} - -/// Owning iterator for StackVec: supports double-ended iteration and is exact-size. -pub struct IntoIter { - /// The current front index of the iterator. - start: usize, - - /// The current back index of the iterator. - end: usize, - - /// The owned vector being iterated. - v: StackVec, -} - -impl Iterator for IntoIter { - type Item = T; - - /// Returns the next element in the iterator. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackVec; - /// - /// let mut v = StackVec::::new(); - /// v.push(1); - /// v.push(2); - /// v.push(3); - /// - /// let mut iter = v.into_iter(); - /// assert_eq!(iter.next(), Some(1)); - /// assert_eq!(iter.next(), Some(2)); - /// assert_eq!(iter.next(), Some(3)); - /// assert_eq!(iter.next(), None); - /// ``` - #[inline] - fn next(&mut self) -> Option { - if self.start < self.end { - let idx = self.start; - self.start += 1; - // take ownership of element - Some(unsafe { self.v.data.get_unchecked(idx).assume_init_read() }) - } else { - None - } - } - - /// Returns the remaining number of elements. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackVec; - /// - /// let mut v = StackVec::::new(); - /// v.extend_from_slice(&[1, 2, 3, 4, 5]); - /// - /// let mut iter = v.into_iter(); - /// assert_eq!(iter.size_hint(), (5, Some(5))); - /// iter.next(); - /// assert_eq!(iter.size_hint(), (4, Some(4))); - /// iter.next_back(); - /// assert_eq!(iter.size_hint(), (3, Some(3))); - /// ``` - #[inline] - fn size_hint(&self) -> (usize, Option) { - let rem = self.end - self.start; - (rem, Some(rem)) - } -} - -impl DoubleEndedIterator for IntoIter { - /// Returns the next element from the back of the iterator. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackVec; - /// - /// let mut v = StackVec::::new(); - /// v.push(1); - /// v.push(2); - /// v.push(3); - /// - /// let mut iter = v.into_iter(); - /// assert_eq!(iter.next(), Some(1)); - /// assert_eq!(iter.next_back(), Some(3)); - /// assert_eq!(iter.next(), Some(2)); - /// assert_eq!(iter.next(), None); - /// ``` - #[inline] - fn next_back(&mut self) -> Option { - if self.start < self.end { - self.end -= 1; - let idx = self.end; - Some(unsafe { self.v.data.get_unchecked(idx).assume_init_read() }) - } else { - None - } - } -} - -impl ExactSizeIterator for IntoIter {} - -impl Drop for IntoIter { - /// Drops the iterator, cleaning up any remaining unyielded elements. - /// - /// This ensures that elements not consumed by the iterator are properly dropped. - #[inline] - fn drop(&mut self) { - // Drop any remaining elements that weren't yielded. - unsafe { - for i in self.start..self.end { - self.v.data.get_unchecked_mut(i).assume_init_drop(); - } - // Prevent the StackVec::Drop from trying to drop them again. - self.v.len = 0; - } - } -} - -impl IntoIterator for StackVec { - type Item = T; - type IntoIter = IntoIter; - - /// Converts the `StackVec` into an owning iterator. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackVec; - /// - /// let mut v = StackVec::::new(); - /// v.push(1); - /// v.push(2); - /// v.push(3); - /// - /// let collected: Vec<_> = v.into_iter().collect(); - /// assert_eq!(collected, vec![1, 2, 3]); - /// ``` - #[inline] - fn into_iter(self) -> Self::IntoIter { - let len = self.len; - IntoIter { - start: 0, - end: len, - v: self, - } - } -} - -impl Deref for StackVec { - type Target = [T]; - - /// Returns a slice reference to the `StackVec`. - /// - /// This is equivalent to [`Self::as_slice`]. - /// - #[inline] - fn deref(&self) -> &Self::Target { - self.as_slice() - } -} - -impl DerefMut for StackVec { - /// Returns a mutable slice reference to the `StackVec`. - /// - /// This is equivalent to [`Self::as_mut_slice`]. - #[inline] - fn deref_mut(&mut self) -> &mut Self::Target { - self.as_mut_slice() - } -} - -impl AsRef<[T]> for StackVec { - /// Returns a reference to the `StackVec` as a slice. - /// - /// This is equivalent to [`Self::as_slice`]. - #[inline] - fn as_ref(&self) -> &[T] { - self - } -} - -impl AsMut<[T]> for StackVec { - /// Returns a mutable slice reference to the `StackVec`. - /// - /// This is equivalent to [`Self::as_mut_slice`]. - #[inline] - fn as_mut(&mut self) -> &mut [T] { - &mut *self - } -} - -impl fmt::Debug for StackVec { - /// Formats the `StackVec` using the `Debug` trait. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackVec; - /// - /// let mut v = StackVec::::new(); - /// v.extend_from_slice(&[1, 2, 3]); - /// - /// assert_eq!(format!("{:?}", v), "[1, 2, 3]"); - /// ``` - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(&**self, f) - } -} - -impl PartialEq for StackVec { - /// Checks if two `StackVec`s are equal element by element. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackVec; - /// - /// let v1: StackVec = [1, 2, 3].iter().copied().collect(); - /// let v2: StackVec = [1, 2, 3].iter().copied().collect(); - /// let v3: StackVec = [1, 2, 4].iter().copied().collect(); - /// - /// assert_eq!(v1, v2); - /// assert_ne!(v1, v3); - /// ``` - #[inline] - fn eq(&self, other: &Self) -> bool { - **self == **other - } -} - -impl Eq for StackVec {} - -impl PartialOrd for StackVec { - /// Performs lexicographic ordering on `StackVec`s, same as slices (`[T]`). - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackVec; - /// - /// let v1: StackVec = [1, 2, 3].iter().copied().collect(); - /// let v2: StackVec = [1, 2, 4].iter().copied().collect(); - /// let v3: StackVec = [1, 2, 3].iter().copied().collect(); - /// - /// assert!(v1 < v2); - /// assert!(v2 > v1); - /// assert!(v1 <= v3); - /// assert!(v1 >= v3); - /// ``` - #[inline] - fn partial_cmp(&self, other: &Self) -> Option { - (**self).partial_cmp(&**other) - } -} - -impl Ord for StackVec { - /// Compares two `StackVec`s lexicographically. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackVec; - /// - /// let v1: StackVec = [1, 2, 3].iter().copied().collect(); - /// let v2: StackVec = [1, 2, 4].iter().copied().collect(); - /// - /// assert!(v1 < v2); - /// assert!(v2 > v1); - /// assert!(v1 <= v1.clone()); - /// assert!(v1 >= v1.clone()); - /// ``` - #[inline] - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - (**self).cmp(&**other) - } -} - -impl Hash for StackVec { - /// Hashes the contents of the `StackVec`. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackVec; - /// use core::hash::{Hash, Hasher}; - /// - /// // simple no_std hasher - /// struct MyTestHasher { - /// hash: u64, - /// } - /// - /// impl Hasher for MyTestHasher { - /// fn write(&mut self, bytes: &[u8]) { - /// for &byte in bytes { - /// self.hash = self.hash.wrapping_add(byte as u64); - /// } - /// } - /// fn finish(&self) -> u64 { - /// self.hash - /// } - /// } - /// - /// let v1: StackVec = [1, 2, 3].iter().copied().collect(); - /// let v2: StackVec = [1, 2, 3].iter().copied().collect(); - /// - /// let mut hasher1 = MyTestHasher { hash: 0 }; - /// let mut hasher2 = MyTestHasher { hash: 0 }; - /// - /// v1.hash(&mut hasher1); - /// v2.hash(&mut hasher2); - /// - /// assert_eq!(hasher1.finish(), hasher2.finish()); - /// ``` - #[inline] - fn hash(&self, state: &mut H) { - (**self).hash(state); - } -} - -impl Clone for StackVec { - /// Creates a copy of the `StackVec`. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackVec; - /// - /// let v1: StackVec = [1, 2, 3].iter().copied().collect(); - /// let v2 = v1.clone(); - /// - /// assert_eq!(v1, v2); - /// - /// // works for zero-sized types as well - /// let mut v3 = StackVec::<(), 8>::new(); - /// v3.push(()); - /// v3.push(()); - /// v3.push(()); - /// - /// let v4 = v3.clone(); - /// assert_eq!(v4.len(), 3); - /// ``` - #[inline] - fn clone(&self) -> Self { - let mut out = Self::new(); - if size_of::() == 0 || mem::needs_drop::() { - for item in self.iter() { - unsafe { out.push_unchecked(item.clone()) } - } - } else { - let dst = out.data.as_mut_ptr() as *mut T; - let src = self.data.as_ptr() as *const T; - unsafe { - ptr::copy_nonoverlapping(src, dst, self.len); - } - out.len = self.len; - } - out - } -} - -impl TryFrom<&[T]> for StackVec { - type Error = &'static str; - - /// Attempts to create a `StackVec` from a slice. - /// - /// Returns an error if the slice length exceeds the stack capacity. - /// - /// # Examples - /// - /// ``` - /// use stack_collections::StackVec; - /// - /// let v = StackVec::::try_from(&[1, 2, 3][..]).unwrap(); - /// assert_eq!(v.len(), 3); - /// assert_eq!(v[0], 1); - /// assert_eq!(v[1], 2); - /// assert_eq!(v[2], 3); - /// - /// let result = StackVec::::try_from(&[1, 2, 3][..]); - /// assert!(result.is_err()); - /// ``` - fn try_from(slice: &[T]) -> Result { - if slice.len() > CAP { - return Err("value length exceeds capacity"); - } - - let mut v = Self::new(); - - unsafe { - if size_of::() == 0 || mem::needs_drop::() { - for item in slice { - v.push_unchecked(item.clone()); - } - } else { - ptr::copy_nonoverlapping( - slice.as_ptr(), - v.data.as_mut_ptr() as *mut T, - slice.len(), - ); - v.len = slice.len(); - } - } - - Ok(v) - } -} - -impl FromIterator for StackVec { - /// Creates a `StackVec` from an iterator. - /// - /// # Panics - /// - /// Panics if the iterator produces more than `CAP` elements. - /// - /// # Examples - /// - /// Basic usage: - /// - /// ``` - /// use stack_collections::StackVec; - /// - /// let v: StackVec = [1, 2, 3].iter().copied().collect(); - /// assert_eq!(v.len(), 3); - /// assert_eq!(v[0], 1); - /// ``` - /// - /// A panic upon overflow - /// ```should_panic - /// use stack_collections::StackVec; - /// - /// // this will panic at runtime - /// let v: StackVec = [1, 2, 3].iter().copied().collect(); - /// ``` - fn from_iter>(iter: I) -> Self { - let mut v = Self::new(); - for item in iter { - assert!(v.len < CAP, "buffer capacity exceeded in FromIterator"); - unsafe { v.push_unchecked(item) } - } - v - } -} - -impl Extend for StackVec { - /// Extends the `StackVec` with elements from an iterator. - /// - /// # Panics - /// - /// Panics if adding all elements would exceed capacity. - /// - /// # Examples - /// - /// Basic usage: - /// - /// ``` - /// use stack_collections::StackVec; - /// - /// let mut v = StackVec::::new(); - /// v.push(1); - /// v.extend([2, 3, 4].iter().copied()); - /// assert_eq!(v.as_slice(), &[1, 2, 3, 4]); - /// ``` - /// - /// Extending zero-sized types: - /// - /// ``` - /// use stack_collections::StackVec; - /// - /// let mut v = StackVec::<(), 8>::new(); - /// v.extend_from_slice(&[(), (), ()]); - /// assert_eq!(v.len(), 3); - /// ``` - /// - /// A panic upon overflow: - /// - /// ```should_panic - /// use stack_collections::StackVec; - /// - /// let mut v = StackVec::::new(); - /// - /// // this will panic at runtime - /// v.extend([1, 2, 3, 4].iter().copied()); - /// ``` - fn extend>(&mut self, iter: I) { - for item in iter { - assert!(self.len < CAP, "buffer capacity exceeded in extend"); - unsafe { self.push_unchecked(item) } - } - } -} - -impl Index for StackVec { - type Output = T; - - /// Returns a reference to the element at `index`. - /// - /// Equivalent to [`Self::index`]. - #[inline] - fn index(&self, index: usize) -> &Self::Output { - self.index(index) - } -} - -impl IndexMut for StackVec { - /// Returns a mutable reference to the element at `index`. - /// - /// Equivalent to [`Self::index_mut`]. - #[inline] - fn index_mut(&mut self, index: usize) -> &mut Self::Output { - self.index_mut(index) - } -} - -#[cfg(test)] -mod tests { - extern crate alloc; - - use super::*; - use alloc::sync::Arc; - use core::sync::atomic::{AtomicUsize, Ordering}; - - // StackString tests - #[test] - fn test_stack_string_multibyte_boundaries() { - let mut s = StackString::<20>::new(); - s.push_str("a"); // 1 byte - s.push('ñ'); // 2 bytes - s.push('€'); // 3 bytes - s.push('𝄞'); // 4 bytes - - assert_eq!(s.len(), 10); - assert_eq!(s.as_str(), "añ€𝄞"); - - assert_eq!(s.pop(), '𝄞'); - assert_eq!(s.len(), 6); - - assert_eq!(s.pop(), '€'); - assert_eq!(s.len(), 3); - - assert_eq!(s.pop(), 'ñ'); - assert_eq!(s.len(), 1); - } - - // StackVec tests - #[test] - fn test_stack_vec_insert_at_boundaries() { - let mut v = StackVec::::new(); - - v.insert(0, 1); - assert_eq!(v[0], 1); - - v.insert(0, 0); - assert_eq!(v.as_slice(), &[0, 1]); - - v.insert(2, 2); - assert_eq!(v.as_slice(), &[0, 1, 2]); - } - - #[test] - fn test_stack_vec_retain_with_drops() { - let counter = Arc::new(AtomicUsize::new(0)); - - struct DropCounter(i32, Arc); - impl Drop for DropCounter { - fn drop(&mut self) { - self.1.fetch_add(1, Ordering::SeqCst); - } - } - - let mut v = StackVec::::new(); - v.push(DropCounter(1, counter.clone())); - v.push(DropCounter(2, counter.clone())); - v.push(DropCounter(3, counter.clone())); - v.push(DropCounter(4, counter.clone())); - - v.retain(|dc| dc.0 % 2 == 0); - - assert_eq!(v.len(), 2); - assert_eq!(counter.load(Ordering::SeqCst), 2); - - drop(v); - assert_eq!(counter.load(Ordering::SeqCst), 4); - } - - #[test] - fn test_stack_vec_truncate_drop() { - let counter = Arc::new(AtomicUsize::new(0)); - - struct DropCounter(Arc); - impl Drop for DropCounter { - fn drop(&mut self) { - self.0.fetch_add(1, Ordering::SeqCst); - } - } - - let mut v = StackVec::::new(); - v.push(DropCounter(counter.clone())); - v.push(DropCounter(counter.clone())); - v.push(DropCounter(counter.clone())); - - v.truncate(1); - assert_eq!(counter.load(Ordering::SeqCst), 2); - - v.clear(); - assert_eq!(counter.load(Ordering::SeqCst), 3); - } - - #[test] - fn test_stack_vec_drop() { - let counter = Arc::new(AtomicUsize::new(0)); - - struct DropCounter(Arc); - impl Drop for DropCounter { - fn drop(&mut self) { - self.0.fetch_add(1, Ordering::SeqCst); - } - } - - { - let mut v = StackVec::::new(); - v.push(DropCounter(counter.clone())); - v.push(DropCounter(counter.clone())); - v.push(DropCounter(counter.clone())); - } - - assert_eq!(counter.load(Ordering::SeqCst), 3); - } - - #[test] - fn test_stack_vec_into_iter_partial_drop() { - let counter = Arc::new(AtomicUsize::new(0)); - - struct DropCounter(Arc); - impl Drop for DropCounter { - fn drop(&mut self) { - self.0.fetch_add(1, Ordering::SeqCst); - } - } - - let mut v = StackVec::::new(); - v.push(DropCounter(counter.clone())); - v.push(DropCounter(counter.clone())); - v.push(DropCounter(counter.clone())); - - let mut iter = v.into_iter(); - iter.next(); - - drop(iter); - assert_eq!(counter.load(Ordering::SeqCst), 3); - } -} diff --git a/src/stack_string.rs b/src/stack_string.rs new file mode 100644 index 0000000..a48b58c --- /dev/null +++ b/src/stack_string.rs @@ -0,0 +1,1585 @@ +use crate::internal::{define_variants, empty_collection}; + +use core::{ + cmp::Ordering, + fmt, + hash::{Hash, Hasher}, + hint::unreachable_unchecked, + mem::MaybeUninit, + ops::{Deref, DerefMut}, + ptr, slice, str, +}; + +#[cfg(feature = "alloc")] +use alloc::{borrow::ToOwned as _, string::String}; +#[cfg(feature = "std")] +use std::io::{self, Write}; + +#[cfg(feature = "nightly")] +use core::ascii::Char; + +/// A UTF-8–encoded, stack-allocated, fixed-capacity string. +pub struct StackString { + /// Internal buffer holding up to `N` bytes of UTF-8 text. + buf: [MaybeUninit; N], + + /// Current string length (in bytes). + len: usize, +} + +impl StackString { + /// Creates a new [`StackString`]. + /// + /// # Examples + /// + /// ``` + /// use stack_collections::StackString; + /// + /// let stack_string = StackString::<32>::new(); + /// assert_eq!(stack_string.len(), 0); + /// assert_eq!(stack_string.capacity(), 32); + /// assert!(stack_string.is_empty()); + /// assert!(!stack_string.is_full()); + /// assert_eq!(stack_string.as_str(), ""); + /// ``` + #[must_use] + #[inline] + pub const fn new() -> Self { + empty_collection!() + } + + /// Returns a raw pointer to the string's buffer. + /// + /// # Examples + /// + /// ``` + /// use stack_collections::StackString; + /// + /// let mut stack_string = StackString::<8>::new(); + /// stack_string.push_str("hello"); + /// + /// let ptr = stack_string.as_ptr(); + /// // SAFETY: ptr points to initialized data at index 0 + /// let first = unsafe { ptr.read() }; + /// assert_eq!(first, b'h'); + /// + /// // SAFETY: computing pointer offset within bounds (5 bytes) + /// let ptr_4 = unsafe { ptr.add(4) }; + /// // SAFETY: ptr_4 points to initialized data at index 4 + /// let fifth = unsafe { ptr_4.read() }; + /// assert_eq!(fifth, b'o'); + /// ``` + #[must_use] + #[inline] + pub const fn as_ptr(&self) -> *const u8 { + self.buf.as_ptr().cast::() + } + + /// Returns a mutable raw pointer to the string's buffer. + /// + /// Using the pointer may be unsafe if: + /// - the string slice is modified in a way that it is no longer valid UTF-8 + /// - the length of the string is changed manually without also updating it via [`Self::set_len`] + /// + /// For safe mutable access, use [`Self::as_mut_str()`] instead. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use stack_collections::StackString; + /// + /// let mut stack_string = StackString::<8>::new(); + /// stack_string.push_str("hello"); + /// assert_eq!(stack_string.len(), 5); + /// + /// // Modify an existing byte + /// let ptr = stack_string.as_mut_ptr(); + /// // SAFETY: converting 'h' to 'H' maintains UTF-8 validity + /// unsafe { + /// ptr.write(b'H'); + /// } + /// assert_eq!(stack_string.len(), 5); + /// assert_eq!(stack_string.as_str(), "Hello"); + /// + /// // Append new bytes manually + /// // SAFETY: computing pointer to end of string + /// let end = unsafe { stack_string.as_mut_ptr().add(stack_string.len()) }; + /// // SAFETY: writing a valid ASCII byte to valid pointer + /// unsafe { + /// end.write(b'!'); + /// } + /// // SAFETY: we wrote one valid UTF-8 byte + /// unsafe { + /// stack_string.set_len(stack_string.len() + 1); + /// } + /// assert_eq!(stack_string.len(), 6); + /// assert_eq!(stack_string.as_str(), "Hello!"); + /// ``` + /// + /// *Incorrect* usage: + /// + /// ```rust,no_run + /// use stack_collections::StackString; + /// + /// let mut stack_string = StackString::<8>::new(); + /// stack_string.push_str("hello"); + /// + /// unsafe { + /// let ptr = stack_string.as_mut_ptr(); + /// + /// // Overwrite the first byte with an invalid UTF-8 start byte + /// ptr.write(0xFF); // Undefined Behavior! ⚠️ + /// + /// // Append a byte without updating the length + /// let end = ptr.add(stack_string.len()); + /// end.write(b'!'); // Undefined Behavior! ⚠️ + /// } + /// ``` + #[must_use] + #[inline] + pub const fn as_mut_ptr(&mut self) -> *mut u8 { + self.buf.as_mut_ptr().cast::() + } + + /// Forces the length of the string. + /// + /// # Safety + /// + /// Calling this function when any of the following conditions are **`true`** is **undefined behavior**: + /// - `new_len > N` + /// - `self.buf` up to `new_len` are not all initialized with valid UTF-8. + /// + /// # Examples + /// + /// ``` + /// use stack_collections::StackString; + /// + /// let mut stack_string = StackString::<8>::new(); + /// let ptr = stack_string.as_mut_ptr(); + /// + /// // SAFETY: copying valid UTF-8 bytes to valid pointer + /// unsafe { + /// core::ptr::copy_nonoverlapping(b"hello".as_ptr(), ptr, 5); + /// } + /// // SAFETY: we wrote 5 bytes of valid UTF-8 + /// unsafe { + /// stack_string.set_len(5); + /// } + /// + /// assert_eq!(stack_string.as_str(), "hello"); + /// ``` + #[inline] + pub const unsafe fn set_len(&mut self, new_len: usize) { + debug_assert!(new_len <= N, "buffer capacity exceeded"); + debug_assert!( + str::from_utf8( + // SAFETY: Creating a slice from initialized buffer up to new_len + unsafe { slice::from_raw_parts(self.as_ptr(), new_len) } + ) + .is_ok(), + "Invalid UTF-8" + ); + + self.len = new_len; + } + + define_variants! { + fn pop(self : &mut Self) -> char, + + normal_brief: "Removes and returns the last char", + try_brief: "Attempts to remove and return the last char", + unchecked_brief_suffix: "without bound checking", + ub_conditions: { + self.is_empty() => "string is empty", + }, + prefixes: { + normal: {pub const}, + unchecked: {pub const}, + try: {pub const}, + }, + unchecked_fn: pop_unchecked, + try_fn: try_pop, + body: { + const TAG_CONT_MASK: u8 = 0b1100_0000; + const TAG_CONT: u8 = 0b1000_0000; + + let mut pos = self.len - 1; + let ptr = self.as_ptr(); + + while pos > 0 { + // SAFETY: self.len > pos > 0 + let byte_ptr = unsafe { ptr.add(pos) }; + // SAFETY: byte_ptr points to initialized data + let byte = unsafe { byte_ptr.read() }; + if (byte & TAG_CONT_MASK) != TAG_CONT { + break; + } + pos -= 1; + } + + // SAFETY: pos is now at UTF-8 character boundary + let first_byte_ptr = unsafe { ptr.add(pos) }; + // SAFETY: reading first byte from valid pointer + let first_byte = unsafe { first_byte_ptr.read() }; + let code = match self.len - pos { + 1 => first_byte as u32, + 2 => { + let b1 = first_byte as u32; + // SAFETY: UTF-8 validity guarantees byte 2 exists + let b2_ptr = unsafe { ptr.add(pos + 1) }; + // SAFETY: Reading from valid pointer b2_ptr + let b2 = unsafe { b2_ptr.read() } as u32; + ((b1 & 0x1F) << 6) | (b2 & 0x3F) + } + 3 => { + let b1 = first_byte as u32; + // SAFETY: UTF-8 validity guarantees byte 2 exists + let b2_ptr = unsafe { ptr.add(pos + 1) }; + // SAFETY: Reading from valid pointer b2_ptr + let b2 = unsafe { b2_ptr.read() } as u32; + // SAFETY: UTF-8 validity guarantees byte 3 exists + let b3_ptr = unsafe { ptr.add(pos + 2) }; + // SAFETY: Reading from valid pointer b3_ptr + let b3 = unsafe { b3_ptr.read() } as u32; + ((b1 & 0x0F) << 12) | ((b2 & 0x3F) << 6) | (b3 & 0x3F) + } + 4 => { + let b1 = first_byte as u32; + // SAFETY: UTF-8 validity guarantees byte 2 exists + let b2_ptr = unsafe { ptr.add(pos + 1) }; + // SAFETY: Reading from valid pointer b2_ptr + let b2 = unsafe { b2_ptr.read() } as u32; + // SAFETY: UTF-8 validity guarantees byte 3 exists + let b3_ptr = unsafe { ptr.add(pos + 2) }; + // SAFETY: Reading from valid pointer b3_ptr + let b3 = unsafe { b3_ptr.read() } as u32; + // SAFETY: UTF-8 validity guarantees byte 4 exists + let b4_ptr = unsafe { ptr.add(pos + 3) }; + // SAFETY: Reading from valid pointer b4_ptr + let b4 = unsafe { b4_ptr.read() } as u32; + ((b1 & 0x07) << 18) | ((b2 & 0x3F) << 12) | ((b3 & 0x3F) << 6) | (b4 & 0x3F) + } + // SAFETY: Valid UTF-8 characters are 1-4 bytes + _ => unsafe { unreachable_unchecked() }, + }; + + self.len = pos; + // SAFETY: code was decoded from valid UTF-8, so it represents a valid char + unsafe { char::from_u32_unchecked(code) } + }, + examples: { + normal: { + /// Basic usage: + /// + ///``` + /// use stack_collections::StackString; + /// + /// let mut stack_string = StackString::<8>::new(); + /// stack_string.push_str("abc"); + /// + /// assert_eq!(stack_string.pop(), 'c'); + /// assert_eq!(stack_string.as_str(), "ab"); + /// assert_eq!(stack_string.len(), 2); + /// + /// assert_eq!(stack_string.pop(), 'b'); + /// assert_eq!(stack_string.pop(), 'a'); + /// assert!(stack_string.is_empty()); + /// ``` + /// + /// Multibyte UTF-8 characters: + /// + ///``` + /// use stack_collections::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)); + } + } + } + } + // push + define_variants! { + fn push(self: &mut Self, value: char) -> (), + + normal_brief: "Appends a char", + try_brief: "Attempts to append a char", + unchecked_brief_suffix: "without bound checking", + ub_conditions: { + self.len() + value.len_utf8() > N => "buffer capacity exceeded", + }, + prefixes: { + normal: {pub const}, + unchecked: {pub const}, + try: {pub const}, + }, + unchecked_fn: push_unchecked, + try_fn: try_push, + body: { + let value_len = value.len_utf8(); + // SAFETY: self.len is within bounds N + let dst = unsafe { self.as_mut_ptr().add(self.len) }; + // SAFETY: Caller guarantees sufficient capacity for value_len bytes + unsafe { + Self::write_char_to_ptr(dst, value, value_len); + } + self.len += value_len; + }, + examples: { + normal: { + /// Basic usage: + /// + /// ``` + /// use stack_collections::StackString; + /// + /// let mut stack_string = StackString::<16>::new(); + /// stack_string.push('a'); + /// stack_string.push('😀'); + /// stack_string.push('z'); + /// assert_eq!(stack_string.as_str(), "a😀z"); + /// assert_eq!(stack_string.len(), 6); + /// ``` + /// + /// A panic upon overflow: + /// ```should_panic + /// use stack_collections::StackString; + /// + /// let mut stack_string = StackString::<1>::new(); + /// stack_string.push('a'); + /// + /// // this will panic at runtime + /// stack_string.push('b'); + /// ``` + } + try: { + /// ``` + /// use stack_collections::StackString; + /// + /// let mut stack_string = StackString::<5>::new(); + /// assert!(stack_string.try_push('a').is_some()); + /// assert_eq!(stack_string.len(), 1); + /// assert!(stack_string.try_push('😀').is_some()); + /// assert_eq!(stack_string.len(), 5); + /// + /// assert!(stack_string.try_push('x').is_none()); + /// assert_eq!(stack_string.as_str(), "a😀"); + /// ``` + } + } + } + + // push_ascii + define_variants! { + #[cfg(feature = "nightly")] + #[cfg_attr(docsrs, doc(cfg(feature = "nightly")))] + fn push_ascii(self: &mut Self, ascii: Char) -> (), + + normal_brief: "Appends an ASCII character", + try_brief: "Attempts to push an ASCII character", + unchecked_brief_suffix: "without bound checking", + ub_conditions: { + self.is_full() => "buffer capacity exceeded", + }, + prefixes: { + normal: {pub const}, + unchecked: {pub const}, + try: {pub const}, + }, + unchecked_fn: push_ascii_unchecked, + try_fn: try_push_ascii, + body: { + // SAFETY: self.len is within bounds and caller guarantees space for 1 more byte + let dst = unsafe { self.as_mut_ptr().add(self.len) }; + // SAFETY: Writing single ASCII byte to valid pointer + unsafe { + dst.write(ascii.to_u8()); + } + self.len += 1; + }, + examples: { + normal: { + /// Basic usage: + /// + /// ``` + /// use stack_collections::StackString; + /// use core::ascii::Char; + /// + /// let mut stack_string = StackString::<16>::new(); + /// stack_string.push_ascii(Char::SmallA); + /// stack_string.push_ascii(Char::SmallZ); + /// assert_eq!(stack_string.as_str(), "az"); + /// assert_eq!(stack_string.len(), 2); + /// ``` + /// + /// A panic upon overflow: + /// + /// ```should_panic + /// use stack_collections::StackString; + /// use core::ascii::Char; + /// + /// let mut stack_string = StackString::<1>::new(); + /// stack_string.push_ascii(Char::SmallA); + /// + /// // this will panic at runtime + /// stack_string.push_ascii(Char::SmallB); + /// ``` + } + try: { + /// ``` + /// use stack_collections::StackString; + /// use core::ascii::Char; + /// + /// let mut stack_string = StackString::<3>::new(); + /// assert!(stack_string.try_push_ascii(Char::SmallA).is_some()); + /// assert_eq!(stack_string.len(), 1); + /// + /// assert!(stack_string.try_push_ascii(Char::SmallB).is_some()); + /// assert_eq!(stack_string.len(), 2); + /// + /// assert!(stack_string.try_push_ascii(Char::SmallX).is_none()); + /// assert_eq!(stack_string.as_str(), "ab"); + /// ``` + } + } + } + + // push_str + #[rustfmt::skip] + 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", + }, + prefixes: { + normal: {pub const}, + unchecked: {pub const}, + try: {pub const}, + }, + unchecked_fn: push_str_unchecked, + try_fn: try_push_str, + body: { + let bytes = string.as_bytes(); + // SAFETY: self.len is within bounds N + let dst = unsafe { self.as_mut_ptr().add(self.len) }; + // SAFETY: Caller guarantees sufficient capacity; copying valid UTF-8 bytes + unsafe { + ptr::copy_nonoverlapping(bytes.as_ptr(), dst, bytes.len()); + } + self.len += bytes.len(); + }, + examples: { + normal: { + /// Basic usage: + /// + /// ``` + /// use stack_collections::StackString; + /// + /// let mut stack_string = StackString::<32>::new(); + /// stack_string.push_str("hello"); + /// assert_eq!(stack_string.as_str(), "hello"); + /// assert_eq!(stack_string.len(), 5); + /// + /// stack_string.push_str(" world"); + /// assert_eq!(stack_string.as_str(), "hello world"); + /// assert_eq!(stack_string.len(), 11); + /// + /// stack_string.push_str(""); + /// assert_eq!(stack_string.as_str(), "hello world"); + /// assert_eq!(stack_string.len(), 11); + /// ``` + /// + /// A panic upon overflow: + /// + /// ```should_panic + /// use stack_collections::StackString; + /// + /// let mut stack_string = StackString::<4>::new(); + /// + /// // this will panic at runtime + /// stack_string.push_str("hello"); + /// ``` + } + try: { + /// ``` + /// use stack_collections::StackString; + /// + /// let mut stack_string = StackString::<8>::new(); + /// assert!(stack_string.try_push_str("hello").is_some()); + /// assert_eq!(stack_string.as_str(), "hello"); + /// + /// assert!(stack_string.try_push_str("world").is_none()); + /// assert_eq!(stack_string.as_str(), "hello"); + /// + /// assert!(stack_string.try_push_str("!!!").is_some()); + /// assert_eq!(stack_string.as_str(), "hello!!!"); + /// ``` + } + } + ); + + // insert + define_variants! { + fn insert(self: &mut Self, index: usize, value: char) -> (), + + normal_brief: "Inserts a `char` at `index`, shifting all elements after it", + 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", + }, + prefixes: { + normal: {pub const}, + unchecked: {pub const}, + try: {pub const}, + }, + unchecked_fn: insert_unchecked, + try_fn: try_insert, + body: { + let value_len = value.len_utf8(); + // SAFETY: Caller guarantees index <= self.len + let dst = unsafe { self.as_mut_ptr().add(index) }; + // SAFETY: Computing destination for shifted elements + let shifted_dst = unsafe { dst.add(value_len) }; + // SAFETY: Shifting elements right by value_len; ranges don't overlap improperly + unsafe { + ptr::copy(dst, shifted_dst, self.len - index); + } + // SAFETY: Caller guarantees sufficient capacity for value_len bytes + unsafe { + Self::write_char_to_ptr(dst, value, value_len); + } + + self.len += value_len; + }, + examples: { + normal: { + /// Basic usage: + /// + /// ``` + /// use stack_collections::StackString; + /// + /// let mut stack_string = StackString::<8>::new(); + /// stack_string.push('a'); + /// stack_string.push('c'); + /// assert_eq!(stack_string.as_str(), "ac"); + /// + /// stack_string.insert(1, 'b'); + /// assert_eq!(stack_string.as_str(), "abc"); + /// assert_eq!(stack_string.len(), 3); + /// + /// stack_string.insert(3, '7'); + /// assert_eq!(stack_string.as_str(), "abc7"); + /// ``` + /// + /// A panic if the index is out of bounds: + /// + /// ```should_panic + /// use stack_collections::StackString; + /// + /// let mut stack_string = StackString::<8>::new(); + /// stack_string.push('a'); + /// stack_string.push('c'); + /// assert_eq!(stack_string.len(), 2); + /// + /// // this will panic at runtime + /// stack_string.insert(3, 'b'); + /// ``` + /// + /// A panic upon overflow: + /// + /// ```should_panic + /// use stack_collections::StackString; + /// + /// let mut stack_string = StackString::<2>::new(); + /// stack_string.push('a'); + /// stack_string.push('c'); + /// assert_eq!(stack_string.len(), 2); + /// + /// // this will panic at runtime + /// stack_string.insert(1, 'b'); + /// ``` + } + try: { + /// ``` + /// use stack_collections::StackString; + /// + /// let mut stack_string = StackString::<8>::new(); + /// + /// stack_string.push_str("abcd"); + /// assert_eq!(stack_string.as_str(), "abcd"); + /// + /// assert!(stack_string.try_insert(1, 'e').is_some()); + /// assert_eq!(stack_string.as_str(), "aebcd"); + /// + /// // index out of bounds + /// assert!(stack_string.try_insert(10, 'x').is_none()); + /// + /// // would exceed capacity + /// stack_string.push_str("!!!"); + /// assert!(stack_string.try_insert(0, 'x').is_none()); + /// ``` + } + } + } + + // insert_ascii + define_variants! { + #[cfg(feature = "nightly")] + #[cfg_attr(docsrs, doc(cfg(feature = "nightly")))] + fn insert_ascii(self: &mut Self, index: usize, ascii: Char) -> (), + + normal_brief: "Inserts an ASCII character at `index`, shifting all elements after it", + 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", + self.is_full() => "buffer capacity exceeded", + }, + prefixes: { + normal: {pub const}, + unchecked: {pub const}, + try: {pub const}, + }, + unchecked_fn: insert_ascii_unchecked, + try_fn: try_insert_ascii, + body: { + // SAFETY: Caller guarantees index <= self.len + let dst = unsafe { self.as_mut_ptr().add(index) }; + // SAFETY: Computing destination for shifted elements + let shifted_dst = unsafe { dst.add(1) }; + // SAFETY: Shifting elements right by 1; dst and dst+1 ranges are valid + unsafe { + ptr::copy(dst, shifted_dst, self.len - index); + } + // SAFETY: Writing single ASCII byte to valid pointer + unsafe { + dst.write(ascii.to_u8()); + } + self.len += 1; + }, + examples: { + normal: { + /// Basic usage: + /// + /// ``` + /// use stack_collections::StackString; + /// use core::ascii::Char; + /// + /// let mut stack_string = StackString::<8>::new(); + /// stack_string.push_ascii(Char::SmallA); + /// stack_string.push_ascii(Char::SmallC); + /// assert_eq!(stack_string.as_str(), "ac"); + /// + /// stack_string.insert_ascii(1, Char::SmallB); + /// assert_eq!(stack_string.as_str(), "abc"); + /// ``` + /// + /// A panic if the index is out of bounds: + /// + /// ```should_panic + /// use stack_collections::StackString; + /// use core::ascii::Char; + /// + /// let mut stack_string = StackString::<8>::new(); + /// stack_string.push_ascii(Char::SmallA); + /// assert_eq!(stack_string.len(), 1); + /// + /// // this will panic at runtime + /// stack_string.insert_ascii(2, Char::SmallB); + /// ``` + /// + /// A panic upon overflow: + /// + /// ```should_panic + /// use stack_collections::StackString; + /// use core::ascii::Char; + /// + /// let mut stack_string = StackString::<1>::new(); + /// stack_string.push_ascii(Char::SmallA); + /// + /// // this will panic at runtime + /// stack_string.insert_ascii(0, Char::SmallB); + /// ``` + } + try: { + /// ``` + /// use stack_collections::StackString; + /// use core::ascii::Char; + /// + /// let mut stack_string = StackString::<3>::new(); + /// assert!(stack_string.try_insert_ascii(0, Char::SmallA).is_some()); + /// assert_eq!(stack_string.as_str(), "a"); + /// + /// assert!(stack_string.try_insert_ascii(1, Char::SmallC).is_some()); + /// assert_eq!(stack_string.as_str(), "ac"); + /// + /// // index out of bounds + /// assert!(stack_string.try_insert_ascii(10, Char::SmallX).is_none()); + /// + /// assert!(stack_string.try_insert_ascii(1, Char::SmallB).is_some()); + /// assert_eq!(stack_string.as_str(), "abc"); + /// + /// // String is full + /// assert!(stack_string.try_insert_ascii(0, Char::SmallX).is_none()); + /// assert_eq!(stack_string.as_str(), "abc"); + /// ``` + } + } + } + + // insert_str + define_variants! { + fn insert_str(self: &mut Self, index: usize, string: &str) -> (), + + normal_brief: "Inserts a `str` at `index`, shifting all elements after it", + 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", + }, + prefixes: { + normal: {pub const}, + unchecked: {pub const}, + try: {pub const}, + }, + unchecked_fn: insert_str_unchecked, + try_fn: try_insert_str, + body: { + let bytes = string.as_bytes(); + // SAFETY: Caller guarantees index <= self.len + let index_ptr = unsafe { self.as_mut_ptr().add(index) }; + + // SAFETY: Caller guarantees self.len + string.len() <= N + let dst = unsafe { index_ptr.add(bytes.len()) }; + // SAFETY: Shifting elements right; ranges are valid + unsafe { + ptr::copy(index_ptr, dst, self.len - index); + } + + // SAFETY: Copying valid UTF-8 bytes; caller guarantees sufficient capacity + unsafe { + ptr::copy_nonoverlapping(bytes.as_ptr(), index_ptr, bytes.len()); + } + self.len += string.len(); + }, + examples: { + normal: { + /// Basic usage: + /// + /// ``` + /// use stack_collections::StackString; + /// + /// let mut stack_string = StackString::<16>::new(); + /// stack_string.push_str("123"); + /// assert_eq!(stack_string.as_str(), "123"); + /// + /// stack_string.insert_str(1, "ABC"); + /// assert_eq!(stack_string.as_str(), "1ABC23"); + /// + /// stack_string.insert_str(5, "DEF"); + /// assert_eq!(stack_string.as_str(), "1ABC2DEF3"); + /// ``` + /// + /// A panic if the index is out of bounds: + /// + /// ```should_panic + /// use stack_collections::StackString; + /// + /// let mut stack_string = StackString::<8>::new(); + /// stack_string.push_str("hello"); + /// assert_eq!(stack_string.len(), 5); + /// + /// // this will panic at runtime + /// stack_string.insert_str(10, "x"); + /// ``` + /// + /// A panic upon overflow: + /// + /// ```should_panic + /// use stack_collections::StackString; + /// + /// let mut stack_string = StackString::<8>::new(); + /// stack_string.push_str("hello"); + /// assert_eq!(stack_string.len(), 5); + /// + /// // this will panic at runtime + /// stack_string.insert_str(0, "world"); + /// ``` + } + try: { + /// ``` + /// use stack_collections::StackString; + /// + /// let mut stack_string = StackString::<12>::new(); + /// stack_string.push_str("hd"); + /// assert_eq!(stack_string.as_str(), "hd"); + /// + /// assert!(stack_string.try_insert_str(1, "el").is_some()); + /// assert_eq!(stack_string.as_str(), "held"); + /// + /// assert!(stack_string.try_insert_str(4, " world").is_some()); + /// assert_eq!(stack_string.as_str(), "held world"); + /// + /// // would exceed capacity + /// assert!(stack_string.try_insert_str(0, "xxx").is_none()); + /// assert_eq!(stack_string.as_str(), "held world"); + /// + /// // index out of bounds + /// assert!(stack_string.try_insert_str(20, "x").is_none()); + /// ``` + } + } + } + + /// 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 { + /// Returns a new empty `StackString`. + /// + /// This is equivalent to [`Self::new`]. + #[inline] + fn default() -> Self { + Self::new() + } +} + +impl Deref for StackString { + type Target = str; + + /// Returns a string slice. + /// + /// This is equivalent to [`Self::as_str`]. + #[inline] + fn deref(&self) -> &Self::Target { + Self::as_str(self) + } +} + +impl DerefMut for StackString { + /// Returns a mutable string slice. + /// + /// This is equivalent to [`Self::as_mut_str`]. + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + Self::as_mut_str(self) + } +} + +impl AsRef for StackString { + /// Returns a string slice. + /// + /// This is equivalent to [`Self::as_str`]. + #[inline] + fn as_ref(&self) -> &str { + Self::as_str(self) + } +} + +impl AsMut for StackString { + /// Borrow the content as a mutable `str`. + /// + /// This is equivalent to [`Self::as_mut_str`]. + #[inline] + fn as_mut(&mut self) -> &mut str { + Self::as_mut_str(self) + } +} + +impl fmt::Debug for StackString { + /// Formats the contents. + /// + /// # Examples + /// + /// ``` + /// use stack_collections::StackString; + /// + /// let stack_string: StackString<16> = "test".try_into().unwrap(); + /// assert_eq!(format!("{:?}", stack_string), "\"test\""); + /// ``` + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self.as_str(), f) + } +} + +impl fmt::Display for StackString { + /// Formats the contents. + /// + /// # Examples + /// + /// ``` + /// use stack_collections::StackString; + /// + /// let stack_string: StackString<16> = "test".try_into().unwrap(); + /// assert_eq!(format!("{}", stack_string), "test"); + /// ``` + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self.as_str(), f) + } +} + +impl PartialEq for StackString { + /// Compares two strings for equality. + /// + /// # Examples + /// + /// ``` + /// use stack_collections::StackString; + /// + /// let s1: StackString<16> = "test".try_into().unwrap(); + /// let s2: StackString<16> = "test".try_into().unwrap(); + /// let s3: StackString<16> = "other".try_into().unwrap(); + /// + /// assert_eq!(s1, s2); + /// assert_ne!(s1, s3); + /// assert_eq!(s1, "test"); + /// assert_eq!(&s1, &"test"); + /// ``` + #[inline] + fn eq(&self, other: &Self) -> bool { + self.as_str() == other.as_str() + } +} + +impl PartialEq for StackString { + #[inline] + fn eq(&self, other: &str) -> bool { + self.as_str() == other + } +} + +impl PartialEq<&str> for StackString { + #[inline] + fn eq(&self, other: &&str) -> bool { + self.as_str() == *other + } +} + +impl Eq for StackString {} + +impl PartialOrd for StackString { + /// Performs lexicographic ordering on the strings. + /// + /// # Examples + /// + /// ``` + /// use stack_collections::StackString; + /// + /// let s1: StackString<8> = "abc".try_into().unwrap(); + /// let s2: StackString<8> = "abd".try_into().unwrap(); + /// let s3: StackString<8> = "abc".try_into().unwrap(); + /// + /// assert!(s1 < s2); + /// assert!(s2 > s1); + /// assert!(s1 <= s3); + /// assert!(s1 >= s3); + /// ``` + #[inline] + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for StackString { + #[inline] + fn cmp(&self, other: &Self) -> Ordering { + self.as_str().cmp(other.as_str()) + } +} + +impl Hash for StackString { + /// Hashes the contents of the string. + /// + /// # Examples + /// + /// ``` + /// use stack_collections::StackString; + /// use core::hash::{Hash, Hasher}; + /// + /// // A simple Hasher for testing purposes + /// struct MyTestHasher { + /// hash: u64, + /// } + /// + /// impl Hasher for MyTestHasher { + /// fn write(&mut self, bytes: &[u8]) { + /// for &byte in bytes { + /// self.hash = self.hash.wrapping_add(byte as u64); + /// } + /// } + /// fn finish(&self) -> u64 { + /// self.hash + /// } + /// } + /// + /// let s1: StackString<16> = "key".try_into().unwrap(); + /// let s2: StackString<16> = "key".try_into().unwrap(); + /// + /// let mut hasher1 = MyTestHasher { hash: 0 }; + /// let mut hasher2 = MyTestHasher { hash: 0 }; + /// + /// s1.hash(&mut hasher1); + /// s2.hash(&mut hasher2); + /// + /// assert_eq!(hasher1.finish(), hasher2.finish()); + /// ``` + #[inline] + fn hash(&self, state: &mut H) { + self.as_str().hash(state); + } +} + +impl TryFrom<&str> for StackString { + type Error = &'static str; + + /// Attempts to create a [`StackString`] from a `str`. + /// + /// Returns an error ("buffer capacity exceeded") if the input string exceeds the buffer capacity. + /// + /// # Examples + /// + /// ``` + /// use stack_collections::StackString; + /// use core::convert::TryFrom; + /// + /// let stack_string: StackString<16> = "hello".try_into().unwrap(); + /// assert_eq!(stack_string.as_str(), "hello"); + /// + /// // Fails if the string is too long for the buffer + /// let result: Result, _> = "hello".try_into(); + /// assert!(result.is_err()); + /// ``` + #[inline] + fn try_from(value: &str) -> Result { + if value.len() > N { + return Err("buffer capacity exceeded"); + } + let mut out = Self::new(); + // SAFETY: We just verified value.len() <= N + unsafe { + out.push_str_unchecked(value); + } + Ok(out) + } +} + +#[cfg(feature = "alloc")] +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] +impl From> for String { + /// Converts a [`StackString`] into a [`String`]. + /// + /// # Examples + /// + /// ``` + /// use stack_collections::StackString; + /// + /// let stack_string: StackString<16> = "hello".try_into().unwrap(); + /// assert_eq!(String::from(stack_string), "hello"); + /// ``` + #[inline] + fn from(value: StackString) -> Self { + value.as_str().to_owned() + } +} + +impl Clone for StackString { + /// Creates a copy of the string. + /// + /// # Examples + /// + /// ``` + /// use stack_collections::StackString; + /// + /// let s1: StackString<16> = "hello".try_into().unwrap(); + /// let s2 = s1.clone(); + /// + /// assert_eq!(s1, s2); + /// assert_eq!(s1.as_str(), s2.as_str()); + /// ``` + #[inline] + fn clone(&self) -> Self { + let mut out = Self::new(); + // SAFETY: self.as_str() fits in new buffer with same capacity N + unsafe { + out.push_str_unchecked(self.as_str()); + } + out + } +} + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +impl Write for StackString { + /// Writes a buffer into the [`StackString`]. + /// + /// Returns the number of bytes written. Per the [`Write`] trait contract: + /// - Returns `Ok(0)` if the buffer is full or input is empty. + /// - Returns `Ok(n)` where `n <= buf.len()` for partial writes. + /// - Only returns [`Err`] if the input would create invalid UTF-8. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use stack_collections::StackString; + /// use std::io::Write; + /// + /// let mut stack_string = StackString::<16>::new(); + /// assert_eq!(stack_string.write(b"Hello").unwrap(), 5); + /// assert_eq!(stack_string.write(b" world!").unwrap(), 7); + /// assert_eq!(stack_string.as_str(), "Hello world!"); + /// ``` + /// + /// Partial writes: + /// + /// ``` + /// use stack_collections::StackString; + /// use std::io::Write; + /// + /// let mut stack_string = StackString::<8>::new(); + /// stack_string.push_str("1234"); + /// + /// let written = stack_string.write(b"567890").unwrap(); + /// assert_eq!(written, 4); + /// assert_eq!(stack_string.as_str(), "12345678"); + /// ``` + #[inline] + fn write(&mut self, buf: &[u8]) -> io::Result { + let available = N - self.len; + if available == 0 || buf.is_empty() { + return Ok(0); + } + + let mut to_write = buf.len().min(available); + + // Walk back to avoid splitting UTF-8 sequences + while to_write > 0 && (buf[to_write - 1] & 0b1100_0000) == 0b1000_0000 { + to_write -= 1; + } + + if to_write == 0 { + return Ok(0); + } + + // Validate the bytes we're writing + str::from_utf8(&buf[..to_write]).map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "invalid UTF-8"))?; + + // SAFETY: self.len is within bounds and we have space for to_write bytes + let dst = unsafe { self.as_mut_ptr().add(self.len) }; + // SAFETY: Copying validated UTF-8 bytes; to_write <= available capacity + unsafe { + ptr::copy_nonoverlapping(buf.as_ptr(), dst, to_write); + } + self.len += to_write; + + Ok(to_write) + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl fmt::Write for StackString { + /// Writes a string slice into the buffer. + /// + /// Returns an error if the string would exceed capacity. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use stack_collections::StackString; + /// use core::fmt::Write; + /// + /// let mut stack_string = StackString::<32>::new(); + /// write!(stack_string, "Hello {}", "world").unwrap(); + /// assert_eq!(stack_string.as_str(), "Hello world"); + /// ``` + /// + /// Error on overflow: + /// + /// ``` + /// use stack_collections::StackString; + /// use core::fmt::Write; + /// + /// let mut stack_string = StackString::<5>::new(); + /// assert!(write!(stack_string, "hello world").is_err()); + /// ``` + #[inline] + fn write_str(&mut self, s: &str) -> fmt::Result { + self.try_push_str(s).ok_or(fmt::Error) + } + + #[inline] + fn write_char(&mut self, c: char) -> fmt::Result { + self.try_push(c).ok_or(fmt::Error) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn multibyte_boundaries() { + let mut stack_string = StackString::<20>::new(); + stack_string.push_str("a"); // 1 byte + stack_string.push('\u{f1}'); + stack_string.push('\u{20ac}'); + stack_string.push('\u{1d11e}'); + + assert_eq!(stack_string.len(), 10); + assert_eq!(stack_string.as_str(), "a\u{f1}\u{20ac}\u{1d11e}"); + + assert_eq!(stack_string.pop(), '\u{1d11e}'); + assert_eq!(stack_string.len(), 6); + + assert_eq!(stack_string.pop(), '\u{20ac}'); + assert_eq!(stack_string.len(), 3); + + assert_eq!(stack_string.pop(), '\u{f1}'); + assert_eq!(stack_string.len(), 1); + } +} diff --git a/src/stack_vec.rs b/src/stack_vec.rs new file mode 100644 index 0000000..a620773 --- /dev/null +++ b/src/stack_vec.rs @@ -0,0 +1,1833 @@ +use crate::internal::{define_variants, empty_collection}; + +use core::{ + cmp::Ordering, + fmt, + hash::{Hash, Hasher}, + mem::{self, MaybeUninit}, + ops::{Deref, DerefMut, Index, IndexMut}, + ptr, slice, +}; + +/// A stack-allocated vector with a fixed capacity. +pub struct StackVec { + /// The underlying storage for elements, allocated on the stack. + buf: [MaybeUninit; CAP], + + /// The current number of initialized elements in the vector. + len: usize, +} + +impl StackVec { + /// Creates a new [`StackVec`]. + /// + /// # Examples + /// + /// ``` + /// use stack_collections::StackVec; + /// + /// let vec = StackVec::::new(); + /// assert_eq!(vec.len(), 0); + /// assert_eq!(vec.capacity(), 8); + /// assert!(vec.is_empty()); + /// assert!(!vec.is_full()); + /// assert_eq!(vec.as_slice(), &[]); + /// ``` + #[must_use] + #[inline] + pub const fn new() -> Self { + empty_collection!() + } + + /// Returns a raw pointer to the vector's buffer. + /// + /// The pointer is valid for reads of `self.len()` elements. + /// + /// # Examples + /// + /// ``` + /// use stack_collections::StackVec; + /// + /// let mut vec = StackVec::::new(); + /// vec.push(1); + /// vec.push(2); + /// vec.push(3); + /// + /// let ptr = vec.as_ptr(); + /// // SAFETY: ptr points to initialized element at index 0 + /// let first = unsafe { ptr.read() }; + /// assert_eq!(first, 1); + /// + /// // SAFETY: computing pointer offset within bounds (len = 3) + /// let ptr_1 = unsafe { ptr.add(1) }; + /// // SAFETY: ptr_1 points to initialized element at index 1 + /// let second = unsafe { ptr_1.read() }; + /// assert_eq!(second, 2); + /// + /// // SAFETY: computing pointer offset within bounds (len = 3) + /// let ptr_2 = unsafe { ptr.add(2) }; + /// // SAFETY: ptr_2 points to initialized element at index 2 + /// let third = unsafe { ptr_2.read() }; + /// assert_eq!(third, 3); + /// ``` + #[expect(clippy::inline_always, reason = "this method is trivial")] + #[must_use] + #[inline(always)] + pub const fn as_ptr(&self) -> *const T { + self.buf.as_ptr().cast::() + } + + /// Returns a mutable raw pointer to the vector's buffer. + /// + /// Using the pointer may be unsafe if: + /// - you write to memory beyond the current length without later calling [`Self::set_len`] + /// - you read from uninitialized elements (i.e. indices >= `self.len()`) + /// - you overwrite an element and break its invariants (e.g. leaving a partially dropped object) + /// + /// For safe mutable access, use [`Self::as_mut_slice`] instead. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use stack_collections::StackVec; + /// + /// let mut vec = StackVec::::new(); + /// vec.push(1); + /// vec.push(2); + /// vec.push(3); + /// + /// let ptr = vec.as_mut_ptr(); + /// // SAFETY: ptr points to initialized element at index 0 + /// unsafe { ptr.write(10) }; + /// + /// let ptr = vec.as_mut_ptr(); + /// // SAFETY: computing pointer offset within bounds (len = 3) + /// let ptr_2 = unsafe { ptr.add(2) }; + /// // SAFETY: ptr_2 points to initialized element at index 2 + /// unsafe { ptr_2.write(30) }; + /// + /// assert_eq!(vec.as_slice(), &[10, 2, 30]); + /// ``` + /// + /// *Incorrect* usage: + /// + /// ```rust,no_run + /// use stack_collections::StackVec; + /// + /// let mut vec = StackVec::::new(); + /// vec.push(1); + /// + /// unsafe { + /// let ptr = vec.as_mut_ptr(); + /// + /// // Writing beyond initialized length without updating len + /// let out_of_bounds = ptr.add(3); // UB: index 3 is uninitialized + /// out_of_bounds.write(99); // Undefined Behavior! ⚠️ + /// + /// // Append an element without updating the length + /// let untracked = ptr.add(1); + /// untracked.write(42); // Undefined Behavior! ⚠️ + /// + /// // Overwriting a live element incorrectly (e.g., if it were a Drop type) + /// ptr.write(100); // UB if old value had invariants or resources + /// } + /// ``` + #[expect(clippy::inline_always, reason = "this method is trivial")] + #[must_use] + #[inline(always)] + pub const fn as_mut_ptr(&mut self) -> *mut T { + self.buf.as_mut_ptr().cast::() + } + + /// Forces the length of the vector. + /// + /// # Safety + /// + /// Calling this function when any of the following conditions are **`true`** is **undefined behavior**: + /// - `new_len > CAP` + /// - `self.buf` up to `new_len` are not all initialized. + /// + /// # Examples + /// + /// ``` + /// use stack_collections::StackVec; + /// + /// let mut vec = StackVec::::new(); + /// let ptr = vec.as_mut_ptr(); + /// + /// // SAFETY: writing to ptr initializes the first element + /// unsafe { ptr.write(10) }; + /// + /// // SAFETY: computing pointer offset within bounds (capacity = 8) + /// let ptr_1 = unsafe { ptr.add(1) }; + /// // SAFETY: writing to ptr_1 initializes the second element + /// unsafe { ptr_1.write(20) }; + /// + /// // SAFETY: computing pointer offset within bounds (capacity = 8) + /// let ptr_2 = unsafe { ptr.add(2) }; + /// // SAFETY: writing to ptr_2 initializes the third element + /// unsafe { ptr_2.write(30) }; + /// + /// // SAFETY: we initialized 3 elements, so it's safe to set len = 3 + /// unsafe { vec.set_len(3) }; + /// + /// assert_eq!(vec.as_slice(), &[10, 20, 30]); + /// ``` + #[inline] + pub const unsafe fn set_len(&mut self, new_len: usize) { + debug_assert!(new_len <= CAP, "buffer capacity exceeded"); + 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); + /// ``` + } + } + } + + // insert + define_variants! { + fn insert(self: &mut Self, index: usize, element: T) -> (), + + 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", + self.is_full() => "buffer capacity exceeded", + }, + prefixes: { + normal: {pub const}, + unchecked: {pub const}, + try: {pub}, + }, + + unchecked_fn: insert_unchecked, + try_fn: try_insert, + 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; + }, + examples: { + normal: { + /// Basic usage: + /// + /// ``` + /// use stack_collections::StackVec; + /// + /// let mut vec = StackVec::::new(); + /// vec.push(1); + /// vec.push(3); + /// assert_eq!(vec.len(), 2); + /// + /// vec.insert(1, 2); + /// assert_eq!(vec.len(), 3); + /// assert_eq!(vec[0], 1); + /// assert_eq!(vec[1], 2); + /// assert_eq!(vec[2], 3); + /// ``` + /// + /// 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.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(); + /// vec.push(10); + /// vec.push(20); + /// + /// 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()); + /// ``` + } + } + } + + // remove + define_variants! { + fn remove(self: &mut Self, index: usize) -> T, + + 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", + }, + prefixes: { + normal: {pub const}, + unchecked: {pub const}, + try: {pub const}, + }, + unchecked_fn: remove_unchecked, + try_fn: try_remove, + 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 + }, + examples: { + normal: { + /// Basic usage: + /// + /// ``` + /// use stack_collections::StackVec; + /// + /// let mut vec = StackVec::::new(); + /// vec.push(1); + /// vec.push(2); + /// vec.push(3); + /// + /// let removed = vec.remove(1); + /// assert_eq!(removed, 2); + /// assert_eq!(vec.len(), 2); + /// assert_eq!(vec[0], 1); + /// assert_eq!(vec[1], 3); + /// ``` + /// + /// 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.remove(1); + /// ``` + } + try: { + /// ``` + /// use stack_collections::StackVec; + /// + /// let mut vec = StackVec::::new(); + /// vec.push(10); + /// + /// assert_eq!(vec.try_remove(0), Some(10)); + /// assert_eq!(vec.try_remove(0), None); + /// ``` + } + } + } + + // pop + define_variants! { + fn pop(self: &mut Self) -> T, + + normal_brief: "Removes and returns the last element", + try_brief: "Attempts to remove and return the last element", + unchecked_brief_suffix: "without bound checking", + ub_conditions: { + self.is_empty() => "vector is empty", + }, + prefixes: { + normal: {pub const}, + unchecked: {pub const}, + try: {pub const}, + }, + unchecked_fn: pop_unchecked, + try_fn: try_pop, + body: { + self.len -= 1; + // SAFETY: self.len was > 0, now points to last initialized element + let ptr = unsafe { self.as_ptr().add(self.len) }; + // SAFETY: Reading from initialized element + unsafe { ptr.read() } + }, + 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.pop(), 3); + /// assert_eq!(vec.len(), 2); + /// assert_eq!(vec.pop(), 2); + /// assert_eq!(vec.pop(), 1); + /// assert!(vec.is_empty()); + /// ``` + /// + /// A panic if the vector is empty: + /// + /// ```should_panic + /// use stack_collections::StackVec; + /// + /// let mut vec = StackVec::::new(); + /// + /// // this will panic at runtime + /// vec.pop(); + /// ``` + } + try: { + /// ``` + /// use stack_collections::StackVec; + /// + /// let mut vec = StackVec::::new(); + /// assert_eq!(vec.try_pop(), None); + /// + /// vec.push(42); + /// assert_eq!(vec.try_pop(), Some(42)); + /// assert_eq!(vec.try_pop(), None); + /// ``` + } + } + } + + // swap_remove + define_variants! { + fn swap_remove(self: &mut Self, index: usize) -> 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)", + unchecked_brief_suffix: "without bound checking", + ub_conditions: { + index >= self.len() => "index out of bounds", + }, + prefixes: { + normal: {pub const}, + unchecked: {pub const}, + try: {pub const}, + }, + unchecked_fn: swap_remove_unchecked, + try_fn: try_swap_remove, + 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); + } + } + 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); + /// ``` + } + } + } + + // 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", + }, + prefixes: { + normal: {pub const}, + unchecked: {pub const}, + try: {pub const}, + }, + unchecked_fn: get_unchecked, + try_fn: get, + 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() } + }, + examples: { + normal: { + /// Basic usage: + /// + /// ``` + /// use stack_collections::StackVec; + /// + /// let mut vec = StackVec::::new(); + /// vec.extend_from_slice(&[10, 20, 30]); + /// + /// assert_eq!(*vec.index(0), 10); + /// assert_eq!(*vec.index(1), 20); + /// assert_eq!(*vec.index(2), 30); + /// ``` + /// + /// Panics on out-of-bounds: + /// + /// ```should_panic + /// use stack_collections::StackVec; + /// let mut vec = StackVec::::new(); + /// + /// // this will panic at runtime + /// vec.index(0); + /// ``` + } + try: { + /// ``` + /// use stack_collections::StackVec; + /// + /// 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); + /// ``` + } + } + } + + // index_mut + define_variants! { + fn index_mut(self: &mut Self, index: usize) -> &mut 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`", + unchecked_brief_suffix: "without bounds checking", + ub_conditions: { + 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, + 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() } + }, + examples: { + normal: { + /// Basic usage: + /// + /// ``` + /// use stack_collections::StackVec; + /// + /// let mut vec = StackVec::::new(); + /// vec.extend_from_slice(&[10, 20, 30]); + /// + /// *vec.index_mut(1) = 42; + /// assert_eq!(vec[1], 42); + /// ``` + /// + /// Panics on out-of-bounds: + /// + /// ```should_panic + /// use stack_collections::StackVec; + /// let mut vec = StackVec::::new(); + /// + /// // this will panic at runtime + /// vec.index_mut(0); + /// ``` + } + try: { + /// ``` + /// use stack_collections::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); + /// ``` + } + } + } + + define_variants! { + fn extend_from_slice(self: &mut Self, slice: &[T]) -> (), + where_clause: { T: Clone } + + 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()); + } + self.len += slice.len(); + } + }, + examples: { + normal: { + /// Basic usage: + /// + /// ``` + /// use stack_collections::StackVec; + /// + /// let mut vec = StackVec::::new(); + /// vec.push(1); + /// vec.extend_from_slice(&[2, 3, 4]); + /// + /// assert_eq!(vec.len(), 4); + /// assert_eq!(vec[0], 1); + /// assert_eq!(vec[1], 2); + /// assert_eq!(vec[2], 3); + /// assert_eq!(vec[3], 4); + /// ``` + /// + /// A panic upon overflow: + /// + /// ```should_panic + /// use stack_collections::StackVec; + /// + /// let mut vec = StackVec::::new(); + /// vec.extend_from_slice(&[1, 2, 3, 4, 5]); + /// ``` + } + try: { + /// ``` + /// use stack_collections::StackVec; + /// + /// let mut vec = StackVec::::new(); + /// vec.push(1); + /// + /// assert!(vec.try_extend_from_slice(&[2, 3]).is_some()); + /// assert_eq!(vec.len(), 3); + /// + /// assert!(vec.try_extend_from_slice(&[4, 5]).is_none()); + /// assert_eq!(vec.len(), 3); + /// ``` + } + } + } + + /// 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 mut kept = 0; + for i in 0..self.len { + // SAFETY: i < self.len, so this is a valid and initialized element + let ptr = unsafe { self.as_mut_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 { self.as_mut_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 { + /// Returns a new empty `StackVec`. + /// + /// This is equivalent to [`Self::new`]. + #[inline] + fn default() -> Self { + Self::new() + } +} + +impl Drop for StackVec { + /// Drops the vector, cleaning up all initialized elements. + /// + /// This ensures that all elements stored in the vector are properly dropped + /// when the vector goes out of scope. + #[inline] + fn drop(&mut self) { + self.drop_elements(0); + } +} + +impl<'vec, T, const CAP: usize> IntoIterator for &'vec StackVec { + type Item = &'vec T; + type IntoIter = slice::Iter<'vec, T>; + + /// Converts the vector into an owning iterator. + /// + /// # Examples + /// + /// ``` + /// use stack_collections::StackVec; + /// + /// let mut vec = StackVec::::new(); + /// vec.push(1); + /// vec.push(2); + /// vec.push(3); + /// + /// let collected: Vec<_> = vec.into_iter().collect(); + /// assert_eq!(collected, vec![1, 2, 3]); + /// ``` + #[inline] + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl<'vec, T, const CAP: usize> IntoIterator for &'vec mut StackVec { + type Item = &'vec mut T; + type IntoIter = slice::IterMut<'vec, T>; + + /// Converts the vector into a mutable iterator. + /// + /// # Examples + /// + /// ``` + /// use stack_collections::StackVec; + /// + /// let mut vec = StackVec::::new(); + /// vec.push(1); + /// vec.push(2); + /// vec.push(3); + /// + /// for x in &mut vec { + /// *x *= 2; + /// } + /// + /// let collected: Vec<_> = vec.iter().copied().collect(); + /// assert_eq!(collected, vec![2, 4, 6]); + /// ``` + #[inline] + fn into_iter(self) -> Self::IntoIter { + self.iter_mut() + } +} + +/// Owning iterator for [`StackVec`]: supports double-ended iteration and is exact-size. +pub struct IntoIter { + /// The current front index of the iterator. + start: usize, + + /// The current back index of the iterator. + end: usize, + + /// The owned vector being iterated. + v: StackVec, +} + +impl Iterator for IntoIter { + type Item = T; + + /// Returns the next element in the iterator. + /// + /// # Examples + /// + /// ``` + /// use stack_collections::StackVec; + /// + /// let mut vec = StackVec::::new(); + /// vec.push(1); + /// vec.push(2); + /// vec.push(3); + /// + /// let mut iter = vec.into_iter(); + /// assert_eq!(iter.next(), Some(1)); + /// assert_eq!(iter.next(), Some(2)); + /// assert_eq!(iter.next(), Some(3)); + /// assert_eq!(iter.next(), None); + /// ``` + #[inline] + fn next(&mut self) -> Option { + (self.start < self.end).then(|| { + let idx = self.start; + self.start += 1; + // SAFETY: idx is within range [start, end) which are valid initialized elements + let elem = unsafe { self.v.buf.get_unchecked(idx) }; + // SAFETY: Taking ownership of initialized element + unsafe { elem.assume_init_read() } + }) + } + + /// Returns the remaining number of elements. + /// + /// # Examples + /// + /// ``` + /// use stack_collections::StackVec; + /// + /// let mut vec = StackVec::::new(); + /// vec.extend_from_slice(&[1, 2, 3, 4, 5]); + /// + /// let mut iter = vec.into_iter(); + /// assert_eq!(iter.size_hint(), (5, Some(5))); + /// iter.next(); + /// assert_eq!(iter.size_hint(), (4, Some(4))); + /// iter.next_back(); + /// assert_eq!(iter.size_hint(), (3, Some(3))); + /// ``` + #[inline] + fn size_hint(&self) -> (usize, Option) { + let rem = self.end - self.start; + (rem, Some(rem)) + } +} + +impl DoubleEndedIterator for IntoIter { + /// Returns the next element from the back of the iterator. + /// + /// # Examples + /// + /// ``` + /// use stack_collections::StackVec; + /// + /// let mut vec = StackVec::::new(); + /// vec.push(1); + /// vec.push(2); + /// vec.push(3); + /// + /// let mut iter = vec.into_iter(); + /// assert_eq!(iter.next(), Some(1)); + /// assert_eq!(iter.next_back(), Some(3)); + /// assert_eq!(iter.next(), Some(2)); + /// assert_eq!(iter.next(), None); + /// ``` + #[inline] + fn next_back(&mut self) -> Option { + (self.start < self.end).then(|| { + self.end -= 1; + let idx = self.end; + // SAFETY: idx is within range [start, end) which are valid initialized elements + let elem_ptr = unsafe { self.v.buf.get_unchecked(idx) }; + // SAFETY: elem_ptr points to an initialized element that we are taking ownership of + unsafe { elem_ptr.assume_init_read() } + }) + } +} + +impl ExactSizeIterator for IntoIter {} + +impl Drop for IntoIter { + /// Drops the iterator, cleaning up any remaining unyielded elements. + /// + /// This ensures that elements not consumed by the iterator are properly dropped. + #[inline] + fn drop(&mut self) { + // Drop any remaining elements that weren't yielded. + for i in self.start..self.end { + // SAFETY: Accessing element `i` is safe because all elements in this range are initialized. + let elem_ptr = unsafe { self.v.buf.get_unchecked_mut(i) }; + + // SAFETY: `elem_ptr` points to an initialized element that hasn't been dropped yet, + // so calling `assume_init_drop()` is safe. + unsafe { + elem_ptr.assume_init_drop(); + } + } + // Prevent the StackVec::Drop from trying to drop them again. + self.v.len = 0; + } +} + +impl IntoIterator for StackVec { + type Item = T; + type IntoIter = IntoIter; + + /// Converts the vector into an owning iterator. + /// + /// # Examples + /// + /// ``` + /// use stack_collections::StackVec; + /// + /// let mut vec = StackVec::::new(); + /// vec.push(1); + /// vec.push(2); + /// vec.push(3); + /// + /// let collected: Vec<_> = vec.into_iter().collect(); + /// assert_eq!(collected, vec![1, 2, 3]); + /// ``` + #[inline] + fn into_iter(self) -> Self::IntoIter { + let len = self.len; + IntoIter { + start: 0, + end: len, + v: self, + } + } +} + +impl Deref for StackVec { + type Target = [T]; + + /// Returns the contents as a slice. + /// + /// This is equivalent to [`Self::as_slice`]. + /// + #[inline] + fn deref(&self) -> &Self::Target { + Self::as_slice(self) + } +} + +impl DerefMut for StackVec { + /// Returns the contents as a mutable slice. + /// + /// This is equivalent to [`Self::as_mut_slice`]. + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + Self::as_mut_slice(self) + } +} + +impl AsRef<[T]> for StackVec { + /// Returns the contents as a slice. + /// + /// This is equivalent to [`Self::as_slice`]. + #[inline] + fn as_ref(&self) -> &[T] { + self + } +} + +impl AsMut<[T]> for StackVec { + /// Returns the contents as a mutable slice. + /// + /// This is equivalent to [`Self::as_mut_slice`]. + #[inline] + fn as_mut(&mut self) -> &mut [T] { + &mut *self + } +} + +impl fmt::Debug for StackVec { + /// Formats the contents. + /// + /// # Examples + /// + /// ``` + /// use stack_collections::StackVec; + /// + /// let mut vec = StackVec::::new(); + /// vec.extend_from_slice(&[1, 2, 3]); + /// + /// assert_eq!(format!("{:?}", vec), "[1, 2, 3]"); + /// ``` + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&**self, f) + } +} + +impl PartialEq for StackVec { + /// Checks if two vectors are equal element by element. + /// + /// # Examples + /// + /// ``` + /// use stack_collections::StackVec; + /// + /// let v1: StackVec = [1, 2, 3].iter().copied().collect(); + /// let v2: StackVec = [1, 2, 3].iter().copied().collect(); + /// let v3: StackVec = [1, 2, 4].iter().copied().collect(); + /// + /// assert_eq!(v1, v2); + /// assert_ne!(v1, v3); + /// ``` + #[inline] + fn eq(&self, other: &Self) -> bool { + **self == **other + } +} + +impl Eq for StackVec {} + +impl PartialOrd for StackVec { + /// Performs lexicographic ordering on the vector contents. + /// + /// # Examples + /// + /// ``` + /// use stack_collections::StackVec; + /// + /// let v1: StackVec = [1, 2, 3].iter().copied().collect(); + /// let v2: StackVec = [1, 2, 4].iter().copied().collect(); + /// let v3: StackVec = [1, 2, 3].iter().copied().collect(); + /// + /// assert!(v1 < v2); + /// assert!(v2 > v1); + /// assert!(v1 <= v3); + /// assert!(v1 >= v3); + /// ``` + #[inline] + fn partial_cmp(&self, other: &Self) -> Option { + (**self).partial_cmp(&**other) + } +} + +impl Ord for StackVec { + /// Compares two vectors lexicographically. + /// + /// # Examples + /// + /// ``` + /// use stack_collections::StackVec; + /// + /// let v1: StackVec = [1, 2, 3].iter().copied().collect(); + /// let v2: StackVec = [1, 2, 4].iter().copied().collect(); + /// + /// assert!(v1 < v2); + /// assert!(v2 > v1); + /// assert!(v1 <= v1.clone()); + /// assert!(v1 >= v1.clone()); + /// ``` + #[inline] + fn cmp(&self, other: &Self) -> Ordering { + (**self).cmp(&**other) + } +} + +impl Hash for StackVec { + /// Hashes the vector contents. + /// + /// # Examples + /// + /// ``` + /// use stack_collections::StackVec; + /// use core::hash::{Hash, Hasher}; + /// + /// // simple no_std hasher + /// struct MyTestHasher { + /// hash: u64, + /// } + /// + /// impl Hasher for MyTestHasher { + /// fn write(&mut self, bytes: &[u8]) { + /// for &byte in bytes { + /// self.hash = self.hash.wrapping_add(byte as u64); + /// } + /// } + /// fn finish(&self) -> u64 { + /// self.hash + /// } + /// } + /// + /// let v1: StackVec = [1, 2, 3].iter().copied().collect(); + /// let v2: StackVec = [1, 2, 3].iter().copied().collect(); + /// + /// let mut hasher1 = MyTestHasher { hash: 0 }; + /// let mut hasher2 = MyTestHasher { hash: 0 }; + /// + /// v1.hash(&mut hasher1); + /// v2.hash(&mut hasher2); + /// + /// assert_eq!(hasher1.finish(), hasher2.finish()); + /// ``` + #[inline] + fn hash(&self, state: &mut H) { + (**self).hash(state); + } +} + +impl Clone for StackVec { + /// Creates a copy of the vector. + /// + /// # Examples + /// + /// ``` + /// use stack_collections::StackVec; + /// + /// let v1: StackVec = [1, 2, 3].iter().copied().collect(); + /// let v2 = v1.clone(); + /// + /// assert_eq!(v1, v2); + /// + /// // works for zero-sized types as well + /// let mut v3 = StackVec::<(), 8>::new(); + /// v3.push(()); + /// v3.push(()); + /// v3.push(()); + /// + /// let v4 = v3.clone(); + /// assert_eq!(v4.len(), 3); + /// ``` + #[inline] + fn clone(&self) -> Self { + let mut out = Self::new(); + if size_of::() == 0 || mem::needs_drop::() { + for item in self { + // SAFETY: self.len <= CAP, so cloning all elements fits + unsafe { out.push_unchecked(item.clone()) } + } + } else { + let dst = out.as_mut_ptr(); + let src = self.as_ptr(); + // SAFETY: Copying self.len initialized elements; both have capacity CAP + unsafe { + ptr::copy_nonoverlapping(src, dst, self.len); + } + out.len = self.len; + } + out + } +} + +impl TryFrom<&[T]> for StackVec { + type Error = &'static str; + + /// Attempts to create a vector from a slice. + /// + /// Returns an error if the slice length exceeds the stack capacity. + /// + /// # Examples + /// + /// ``` + /// use stack_collections::StackVec; + /// + /// let v = StackVec::::try_from(&[1, 2, 3][..]).unwrap(); + /// assert_eq!(v.len(), 3); + /// assert_eq!(v[0], 1); + /// assert_eq!(v[1], 2); + /// assert_eq!(v[2], 3); + /// + /// let result = StackVec::::try_from(&[1, 2, 3][..]); + /// assert!(result.is_err()); + /// ``` + #[inline] + fn try_from(slice: &[T]) -> Result { + if slice.len() > CAP { + return Err("value length exceeds capacity"); + } + + let mut vec = Self::new(); + + if size_of::() == 0 || mem::needs_drop::() { + for item in slice { + // SAFETY: We verified slice.len() <= CAP + unsafe { + vec.push_unchecked(item.clone()); + } + } + } else { + // SAFETY: Copying slice.len() elements which we verified <= CAP + unsafe { + ptr::copy_nonoverlapping(slice.as_ptr(), vec.as_mut_ptr(), slice.len()); + } + vec.len = slice.len(); + } + + Ok(vec) + } +} + +impl FromIterator for StackVec { + /// Creates a vector from an iterator. + /// + /// # Panics + /// + /// A panic if the iterator produces more than `CAP` elements. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use stack_collections::StackVec; + /// + /// let v: StackVec = [1, 2, 3].iter().copied().collect(); + /// assert_eq!(v.len(), 3); + /// assert_eq!(v[0], 1); + /// ``` + /// + /// A panic upon overflow + /// ```should_panic + /// use stack_collections::StackVec; + /// + /// // this will panic at runtime + /// let v: StackVec = [1, 2, 3].iter().copied().collect(); + /// ``` + #[inline] + fn from_iter>(iter: I) -> Self { + let mut vec = Self::new(); + for item in iter { + assert!(vec.len < CAP, "buffer capacity exceeded in FromIterator"); + // SAFETY: We just verified vec.len < CAP + unsafe { vec.push_unchecked(item) } + } + vec + } +} + +impl Extend for StackVec { + /// Extends the vector with elements from an iterator. + /// + /// # Panics + /// + /// A panic if adding all elements would exceed capacity. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use stack_collections::StackVec; + /// + /// let mut vec = StackVec::::new(); + /// vec.push(1); + /// vec.extend([2, 3, 4].iter().copied()); + /// assert_eq!(vec.as_slice(), &[1, 2, 3, 4]); + /// ``` + /// + /// Extending zero-sized types: + /// + /// ``` + /// use stack_collections::StackVec; + /// + /// let mut vec = StackVec::<(), 8>::new(); + /// vec.extend_from_slice(&[(), (), ()]); + /// assert_eq!(vec.len(), 3); + /// ``` + /// + /// A panic upon overflow: + /// + /// ```should_panic + /// use stack_collections::StackVec; + /// + /// let mut vec = StackVec::::new(); + /// + /// // this will panic at runtime + /// vec.extend([1, 2, 3, 4].iter().copied()); + /// ``` + #[inline] + fn extend>(&mut self, iter: I) { + for item in iter { + assert!(self.len < CAP, "buffer capacity exceeded in extend"); + // SAFETY: We just verified self.len < CAP + unsafe { self.push_unchecked(item) } + } + } +} + +impl Index for StackVec { + type Output = T; + + /// Returns a reference to the element at `index`. + /// + /// Equivalent to [`Self::index`]. + #[inline] + fn index(&self, index: usize) -> &Self::Output { + Self::index(self, index) + } +} + +impl IndexMut for StackVec { + /// Returns a mutable reference to the element at `index`. + /// + /// Equivalent to [`Self::index_mut`]. + #[inline] + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + Self::index_mut(self, index) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloc::sync::Arc; + use core::sync::atomic::{AtomicUsize, Ordering}; + + #[test] + fn insert_at_boundaries() { + let mut vec = StackVec::::new(); + + vec.insert(0, 1_i32); + assert_eq!(vec[0], 1_i32); + + vec.insert(0, 0_i32); + assert_eq!(vec.as_slice(), &[0_i32, 1_i32]); + + vec.insert(2, 2); + assert_eq!(vec.as_slice(), &[0_i32, 1_i32, 2_i32]); + } + + #[test] + fn retain_with_drops() { + struct DropCounter(i32, Arc); + impl Drop for DropCounter { + fn drop(&mut self) { + self.1.fetch_add(1, Ordering::SeqCst); + } + } + + let counter = Arc::new(AtomicUsize::new(0)); + let mut vec = StackVec::::new(); + vec.push(DropCounter(1, Arc::clone(&counter))); + vec.push(DropCounter(2, Arc::clone(&counter))); + vec.push(DropCounter(3, Arc::clone(&counter))); + vec.push(DropCounter(4, Arc::clone(&counter))); + + vec.retain(|dc| (dc.0 & 1_i32) == 0); + + assert_eq!(vec.len(), 2); + assert_eq!(counter.load(Ordering::SeqCst), 2); + + drop(vec); + assert_eq!(counter.load(Ordering::SeqCst), 4); + } + + #[test] + fn truncate_drop() { + struct DropCounter(Arc); + impl Drop for DropCounter { + fn drop(&mut self) { + self.0.fetch_add(1, Ordering::SeqCst); + } + } + + let counter = Arc::new(AtomicUsize::new(0)); + let mut vec = StackVec::::new(); + vec.push(DropCounter(Arc::clone(&counter))); + vec.push(DropCounter(Arc::clone(&counter))); + vec.push(DropCounter(Arc::clone(&counter))); + + vec.truncate(1); + assert_eq!(counter.load(Ordering::SeqCst), 2); + + vec.clear(); + assert_eq!(counter.load(Ordering::SeqCst), 3); + } + + #[test] + fn main_drop() { + struct DropCounter(Arc); + impl Drop for DropCounter { + fn drop(&mut self) { + self.0.fetch_add(1, Ordering::SeqCst); + } + } + + let counter = Arc::new(AtomicUsize::new(0)); + { + let mut vec = StackVec::::new(); + vec.push(DropCounter(Arc::clone(&counter))); + vec.push(DropCounter(Arc::clone(&counter))); + vec.push(DropCounter(Arc::clone(&counter))); + + // At this point, nothing is dropped yet + assert_eq!(counter.load(Ordering::SeqCst), 0); + } // vec goes out of scope here + assert_eq!(counter.load(Ordering::SeqCst), 3); + } + + #[test] + fn into_iter_partial_drop() { + struct DropCounter(Arc); + impl Drop for DropCounter { + fn drop(&mut self) { + self.0.fetch_add(1, Ordering::SeqCst); + } + } + + let counter = Arc::new(AtomicUsize::new(0)); + let mut vec = StackVec::::new(); + vec.push(DropCounter(Arc::clone(&counter))); + vec.push(DropCounter(Arc::clone(&counter))); + vec.push(DropCounter(Arc::clone(&counter))); + + let mut iter = vec.into_iter(); + iter.next(); + + drop(iter); + assert_eq!(counter.load(Ordering::SeqCst), 3); + } +} From f88df6399a52fe406368cd2f12c54dfebce6d57b Mon Sep 17 00:00:00 2001 From: OverwrittenCode Date: Sat, 4 Oct 2025 23:26:14 +0100 Subject: [PATCH 2/8] chore: remove redundant semicolon --- src/stack_vec.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/stack_vec.rs b/src/stack_vec.rs index a620773..14d6372 100644 --- a/src/stack_vec.rs +++ b/src/stack_vec.rs @@ -752,7 +752,7 @@ impl StackVec { for item in slice { // SAFETY: Caller guarantees sufficient capacity unsafe { - self.push_unchecked(item.clone());; + self.push_unchecked(item.clone()); } } } else { @@ -908,7 +908,7 @@ impl StackVec { /// # Examples /// /// ``` - /// use stack_collections::StackVec;; + /// use stack_collections::StackVec; /// /// let mut vec = StackVec::::new(); /// vec.push(1); @@ -930,7 +930,7 @@ impl StackVec { /// # Examples /// /// ``` - /// use stack_collections::StackVec;; + /// use stack_collections::StackVec; /// /// let mut vec = StackVec::::new(); /// vec.push(1); From 7666c38f1ffd5d653fa27af1799afbf1716a7d4e Mon Sep 17 00:00:00 2001 From: OverwrittenCode Date: Sat, 4 Oct 2025 23:29:31 +0100 Subject: [PATCH 3/8] fix: add extern crate --- src/stack_vec.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/stack_vec.rs b/src/stack_vec.rs index 14d6372..0bdbdaa 100644 --- a/src/stack_vec.rs +++ b/src/stack_vec.rs @@ -1722,6 +1722,8 @@ impl IndexMut for StackVec { #[cfg(test)] mod tests { + extern crate alloc; + use super::*; use alloc::sync::Arc; use core::sync::atomic::{AtomicUsize, Ordering}; From 339a11b642f48b138f895b16968292c88dfc980d Mon Sep 17 00:00:00 2001 From: OverwrittenCode Date: Sat, 4 Oct 2025 23:36:06 +0100 Subject: [PATCH 4/8] fix: add unstable library feature to tests --- src/stack_string.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/stack_string.rs b/src/stack_string.rs index a48b58c..69bb6ee 100644 --- a/src/stack_string.rs +++ b/src/stack_string.rs @@ -514,6 +514,9 @@ impl StackString { /// Basic usage: /// /// ``` + /// #![feature(ascii_char)] + /// #![feature(ascii_char_variants)] + /// /// use stack_collections::StackString; /// use core::ascii::Char; /// @@ -527,6 +530,9 @@ impl StackString { /// A panic upon overflow: /// /// ```should_panic + /// #![feature(ascii_char)] + /// #![feature(ascii_char_variants)] + /// /// use stack_collections::StackString; /// use core::ascii::Char; /// @@ -539,6 +545,9 @@ impl StackString { } try: { /// ``` + /// #![feature(ascii_char)] + /// #![feature(ascii_char_variants)] + /// /// use stack_collections::StackString; /// use core::ascii::Char; /// @@ -780,6 +789,9 @@ impl StackString { /// Basic usage: /// /// ``` + /// #![feature(ascii_char)] + /// #![feature(ascii_char_variants)] + /// /// use stack_collections::StackString; /// use core::ascii::Char; /// @@ -795,6 +807,9 @@ impl StackString { /// A panic if the index is out of bounds: /// /// ```should_panic + /// #![feature(ascii_char)] + /// #![feature(ascii_char_variants)] + /// /// use stack_collections::StackString; /// use core::ascii::Char; /// @@ -809,6 +824,9 @@ impl StackString { /// A panic upon overflow: /// /// ```should_panic + /// #![feature(ascii_char)] + /// #![feature(ascii_char_variants)] + /// /// use stack_collections::StackString; /// use core::ascii::Char; /// @@ -821,6 +839,9 @@ impl StackString { } try: { /// ``` + /// #![feature(ascii_char)] + /// #![feature(ascii_char_variants)] + /// /// use stack_collections::StackString; /// use core::ascii::Char; /// From b44cd88539a70eec10d68ac915d5927c0c11b0c1 Mon Sep 17 00:00:00 2001 From: OverwrittenCode Date: Sat, 4 Oct 2025 23:49:02 +0100 Subject: [PATCH 5/8] fix: correct test and address miri UB --- src/stack_string.rs | 2 +- src/stack_vec.rs | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/stack_string.rs b/src/stack_string.rs index 69bb6ee..d9999c4 100644 --- a/src/stack_string.rs +++ b/src/stack_string.rs @@ -551,7 +551,7 @@ impl StackString { /// use stack_collections::StackString; /// use core::ascii::Char; /// - /// let mut stack_string = StackString::<3>::new(); + /// let mut stack_string = StackString::<2>::new(); /// assert!(stack_string.try_push_ascii(Char::SmallA).is_some()); /// assert_eq!(stack_string.len(), 1); /// diff --git a/src/stack_vec.rs b/src/stack_vec.rs index 0bdbdaa..f96fa5b 100644 --- a/src/stack_vec.rs +++ b/src/stack_vec.rs @@ -836,16 +836,19 @@ impl StackVec { 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 { self.as_mut_ptr().add(i) }; + 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 { self.as_mut_ptr().add(kept) }; + let dst = unsafe { base_ptr.add(kept) }; // SAFETY: Copying single element from valid source to valid dest unsafe { ptr::copy_nonoverlapping(ptr, dst, 1); @@ -862,7 +865,6 @@ impl StackVec { self.len = kept; } - /// Returns the contents as a slice. /// /// # Examples From 14afacc900ce7dfe454a3163484b521d61f9ddaf Mon Sep 17 00:00:00 2001 From: OverwrittenCode Date: Sat, 4 Oct 2025 23:51:32 +0100 Subject: [PATCH 6/8] chore: always run clippy in rust.yml --- .github/workflows/rust.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 06307df..739ceca 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -100,7 +100,6 @@ jobs: clippy: name: Clippy runs-on: ubuntu-latest - if: github.event_name != 'pull_request' timeout-minutes: 45 steps: - uses: actions/checkout@v4 From f0bec41e2a42d0d44385f96b0cf48ce505774c7d Mon Sep 17 00:00:00 2001 From: OverwrittenCode Date: Sat, 4 Oct 2025 23:55:44 +0100 Subject: [PATCH 7/8] fix: use nightly toolchain for clippy --- .github/workflows/rust.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 739ceca..c705027 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -104,7 +104,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable + - uses: dtolnay/rust-toolchain@nightly with: components: clippy From bf601dc6b53e5f16ea9b06699e02d922274753f5 Mon Sep 17 00:00:00 2001 From: OverwrittenCode Date: Sun, 5 Oct 2025 00:10:24 +0100 Subject: [PATCH 8/8] chore: modify description in rust.yml --- .github/workflows/rust.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index c705027..3dd23b7 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -111,7 +111,7 @@ jobs: - name: Clippy (all features) run: cargo clippy --all-targets --all-features -- -Dclippy::all -Dclippy::pedantic -Dclippy::nursery - # Format check + # Rustfmt check fmt: name: Rustfmt runs-on: ubuntu-latest