From 866b25fb8079345eef1a587d81eaeea942daa637 Mon Sep 17 00:00:00 2001 From: Andrei Avram <6795248+andreiavrammsd@users.noreply.github.com> Date: Sun, 18 May 2025 11:15:41 +0300 Subject: [PATCH 1/6] Split errors --- src/lib.rs | 44 ++++++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0f90248..34ab73a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,16 +4,32 @@ use core::{array, mem::MaybeUninit}; -/// Error type returned by [`StaticVector`]. +/// Attempted to push to a full vector #[derive(Debug)] -pub enum Error { - /// Attempted to push to a full vector. - CapacityExceeded, +#[non_exhaustive] +pub struct CapacityExceededError; - /// Attempted to resize the vector to a length greater than its fixed capacity. - LengthTooLarge, +impl core::fmt::Display for CapacityExceededError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str("attempted to push to a full vector") + } +} + +impl core::error::Error for CapacityExceededError {} + +/// Attempted to resize the vector to a length greater than its fixed capacity. +#[derive(Debug)] +#[non_exhaustive] +pub struct LengthTooLargeError; + +impl core::fmt::Display for LengthTooLargeError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str("attempted to resize the vector to a length greater than its fixed capacity") + } } +impl core::error::Error for LengthTooLargeError {} + /// A stack-allocated vector with fixed capacity and dynamic length. /// /// See crate-level documentation for details and usage. @@ -62,10 +78,10 @@ impl StaticVector { /// /// # Errors /// - /// Returns [`Error::CapacityExceeded`] if the vector is already at full capacity. - pub fn push(&mut self, value: &T) -> Result<(), Error> { + /// Returns [`CapacityExceededError`] if the vector is already at full capacity. + pub fn push(&mut self, value: &T) -> Result<(), CapacityExceededError> { if self.length == CAPACITY { - return Err(Error::CapacityExceeded); + return Err(CapacityExceededError); } self.data[self.length].write(value.clone()); @@ -89,13 +105,13 @@ impl StaticVector { /// /// # Errors /// - /// Returns [`Error::LengthTooLarge`] if `new_length` exceeds the vector's fixed capacity. - pub fn set_len(&mut self, new_length: usize) -> Result<(), Error> + /// Returns [`LengthTooLargeError`] if `new_length` exceeds the vector's fixed capacity. + pub fn set_len(&mut self, new_length: usize) -> Result<(), LengthTooLargeError> where T: Default, { if new_length > CAPACITY { - return Err(Error::LengthTooLarge); + return Err(LengthTooLargeError); } if new_length > self.length { @@ -249,7 +265,7 @@ mod tests { let mut vec = StaticVector::::new(); assert!(vec.push(&1).is_ok()); assert!(vec.push(&2).is_ok()); - assert!(matches!(vec.push(&3), Err(Error::CapacityExceeded))); + assert!(matches!(vec.push(&3), Err(CapacityExceededError))); assert_eq!(vec.get(0).unwrap(), &1); assert_eq!(vec.get(1).unwrap(), &2); @@ -271,7 +287,7 @@ mod tests { assert_eq!(vec.len(), 1); assert!(!vec.is_empty()); - assert!(matches!(vec.set_len(100), Err(Error::LengthTooLarge))); + assert!(matches!(vec.set_len(100), Err(LengthTooLargeError))); vec.clear(); assert_eq!(vec.len(), 0); From 96b35a64918e8658f73708c333f0b50e4b2e2161 Mon Sep 17 00:00:00 2001 From: Andrei Avram <6795248+andreiavrammsd@users.noreply.github.com> Date: Sun, 18 May 2025 11:19:54 +0300 Subject: [PATCH 2/6] Rename StaticVector to Vec --- README.md | 12 +++++----- benches/static_vector.rs | 4 ++-- src/lib.rs | 52 ++++++++++++++++++++-------------------- 3 files changed, 34 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 6d30a20..f76dc9f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# StaticVector +# Static Vector [![license](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) [![build](https://github.com/andreiavrammsd/static_vector.rs/workflows/CI/badge.svg)](https://github.com/andreiavrammsd/static_vector.rs/actions/workflows/ci.yml) @@ -6,7 +6,7 @@ A no-std, stack-allocated vector with fixed capacity and dynamic length. -[`StaticVector`] stores elements on the stack using a fixed-size array without heap allocations. +[`Vec`] stores elements on the stack using a fixed-size array without heap allocations. Aims to be suitable for low-level projects and to have an API as safe and explicit as possible. The goal is to allocate only when needed. When first constructed, the vector will not allocate. @@ -21,8 +21,8 @@ The goal is to allocate only when needed. When first constructed, the vector wil ## Requirements -- `T: Clone` for insertion: [`StaticVector::push()`] -- `T: Default` only if [`StaticVector::set_len()`] is used +- `T: Clone` for insertion: [`Vec::push()`] +- `T: Default` only if [`Vec::set_len()`] is used - `CAPACITY > 0` ## Complexity @@ -37,9 +37,9 @@ All operations are O(1) except: ## Example ```rust -use static_vector::StaticVector; +use static_vector::Vec; -let mut vec = StaticVector::::new(); +let mut vec = Vec::::new(); vec.push(&4).unwrap(); vec.push(&5).unwrap(); diff --git a/benches/static_vector.rs b/benches/static_vector.rs index eea1a4c..5fecfdc 100644 --- a/benches/static_vector.rs +++ b/benches/static_vector.rs @@ -1,8 +1,8 @@ use criterion::{Criterion, criterion_group, criterion_main}; -use static_vector::StaticVector; +use static_vector::Vec; fn bench_static_vector(c: &mut Criterion) { - let mut vec = StaticVector::::new(); + let mut vec = Vec::::new(); c.bench_function("push and clear", |b| { b.iter(|| { diff --git a/src/lib.rs b/src/lib.rs index 34ab73a..69e55d8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,22 +33,22 @@ impl core::error::Error for LengthTooLargeError {} /// A stack-allocated vector with fixed capacity and dynamic length. /// /// See crate-level documentation for details and usage. -pub struct StaticVector { +pub struct Vec { data: [MaybeUninit; CAPACITY], length: usize, } -impl Default for StaticVector { - /// Creates an empty [`StaticVector`]. Equivalent to [`StaticVector::new()`]. +impl Default for Vec { + /// Creates an empty [`Vec`]. Equivalent to [`Vec::new()`]. fn default() -> Self { Self::new() } } -impl StaticVector { +impl Vec { const ASSERT_CAPACITY: () = assert!(CAPACITY > 0); - /// Creates a new empty [`StaticVector`] with maximum `CAPACITY` elements of type `T`. + /// Creates a new empty [`Vec`] with maximum `CAPACITY` elements of type `T`. #[inline] pub fn new() -> Self { let () = Self::ASSERT_CAPACITY; @@ -177,15 +177,15 @@ impl StaticVector { } } -impl Drop for StaticVector { +impl Drop for Vec { fn drop(&mut self) { self.drop(0, self.length); } } -/// Immutable iterator over a [`StaticVector`]. +/// Immutable iterator over a [`Vec`]. /// -/// Created by calling [`StaticVector::iter()`]. +/// Created by calling [`Vec::iter()`]. #[must_use = "must consume iterator"] pub struct StaticVectorIterator<'a, T> { data: &'a [MaybeUninit], @@ -207,9 +207,9 @@ impl<'a, T> Iterator for StaticVectorIterator<'a, T> { } } -/// Mutable iterator over a [`StaticVector`]. +/// Mutable iterator over a [`Vec`]. /// -/// Created by calling [`StaticVector::iter_mut()`]. +/// Created by calling [`Vec::iter_mut()`]. #[must_use = "must consume iterator"] pub struct StaticVectorMutableIterator<'a, T> { data: &'a mut [MaybeUninit], @@ -240,16 +240,16 @@ mod tests { #[test] fn construct() { - assert!(StaticVector::::new().is_empty()); - assert!(StaticVector::::default().is_empty()); + assert!(Vec::::new().is_empty()); + assert!(Vec::::default().is_empty()); // Will not build because CAPACITY must be greater than zero - // StaticVector::::new().is_empty(); + // Vec::::new().is_empty(); } #[test] fn capacity() { - let mut vec = StaticVector::::new(); + let mut vec = Vec::::new(); assert_eq!(vec.capacity(), 3); @@ -262,7 +262,7 @@ mod tests { #[test] fn push() { - let mut vec = StaticVector::::new(); + let mut vec = Vec::::new(); assert!(vec.push(&1).is_ok()); assert!(vec.push(&2).is_ok()); assert!(matches!(vec.push(&3), Err(CapacityExceededError))); @@ -274,7 +274,7 @@ mod tests { #[test] fn size() { - let mut vec = StaticVector::::new(); + let mut vec = Vec::::new(); assert_eq!(vec.len(), 0); assert!(vec.is_empty()); @@ -296,7 +296,7 @@ mod tests { #[test] fn get() { - let mut vec = StaticVector::::new(); + let mut vec = Vec::::new(); assert!(vec.first().is_none()); assert!(vec.last().is_none()); assert!(vec.get(0).is_none()); @@ -324,7 +324,7 @@ mod tests { #[test] fn iter() { - let mut vec = StaticVector::::new(); + let mut vec = Vec::::new(); for i in 1..8 { vec.push(&i).unwrap() } @@ -335,7 +335,7 @@ mod tests { #[test] fn iter_mut() { - let mut vec = StaticVector::::new(); + let mut vec = Vec::::new(); for i in 1..8 { vec.push(&i).unwrap() } @@ -374,20 +374,20 @@ mod tests { #[test] fn construct_should_not_create_default_elements() { - let _ = StaticVector::::new(); + let _ = Vec::::new(); assert_eq!(DEFAULTS.get(), 0); } #[test] fn push_should_not_create_default_elements() { - let mut vec = StaticVector::::new(); + let mut vec = Vec::::new(); vec.push(&Struct {}).unwrap(); assert_eq!(DEFAULTS.get(), 0); } #[test] fn set_len_should_create_default_elements() { - let mut vec = StaticVector::::new(); + let mut vec = Vec::::new(); // Length zero, no defaults vec.set_len(0).unwrap(); @@ -414,7 +414,7 @@ mod tests { #[test] fn push_should_clone_element() { - let mut vec = StaticVector::::new(); + let mut vec = Vec::::new(); vec.push(&Struct {}).unwrap(); assert_eq!(CLONES.get(), 1); @@ -426,7 +426,7 @@ mod tests { #[test] fn clear_should_drop_all_allocated_elements() { - let mut vec = StaticVector::::new(); + let mut vec = Vec::::new(); assert_eq!(DROPS.get(), 0); let s = Struct::default(); @@ -441,7 +441,7 @@ mod tests { #[test] fn set_len_should_drop_all_allocated_elements() { - let mut vec = StaticVector::::new(); + let mut vec = Vec::::new(); assert_eq!(DROPS.get(), 0); let s = Struct::default(); @@ -477,7 +477,7 @@ mod tests { let s = Struct::default(); { - let mut vec = StaticVector::::new(); + let mut vec = Vec::::new(); assert_eq!(DROPS.get(), 0); for _ in 1..4 { From 01f9840f4e97ff6eac5db603783f2dd58e415b14 Mon Sep 17 00:00:00 2001 From: Andrei Avram <6795248+andreiavrammsd@users.noreply.github.com> Date: Sun, 18 May 2025 11:22:35 +0300 Subject: [PATCH 3/6] Rename iterators to Iter/IterMut --- src/lib.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 69e55d8..ed462dd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -158,14 +158,14 @@ impl Vec { /// Returns an iterator over immutable references to the elements in the vector. #[inline(always)] - pub fn iter(&self) -> StaticVectorIterator { - StaticVectorIterator { data: &self.data, size: self.length, index: 0 } + pub fn iter(&self) -> Iter { + Iter { data: &self.data, size: self.length, index: 0 } } /// Returns an iterator over mutable references to the elements in the vector. #[inline(always)] - pub fn iter_mut(&mut self) -> StaticVectorMutableIterator { - StaticVectorMutableIterator { data: &mut self.data, size: self.length, index: 0 } + pub fn iter_mut(&mut self) -> IterMut { + IterMut { data: &mut self.data, size: self.length, index: 0 } } fn drop(&mut self, from: usize, to: usize) { @@ -187,13 +187,13 @@ impl Drop for Vec { /// /// Created by calling [`Vec::iter()`]. #[must_use = "must consume iterator"] -pub struct StaticVectorIterator<'a, T> { +pub struct Iter<'a, T> { data: &'a [MaybeUninit], size: usize, index: usize, } -impl<'a, T> Iterator for StaticVectorIterator<'a, T> { +impl<'a, T> Iterator for Iter<'a, T> { type Item = &'a T; fn next(&mut self) -> Option { @@ -211,13 +211,13 @@ impl<'a, T> Iterator for StaticVectorIterator<'a, T> { /// /// Created by calling [`Vec::iter_mut()`]. #[must_use = "must consume iterator"] -pub struct StaticVectorMutableIterator<'a, T> { +pub struct IterMut<'a, T> { data: &'a mut [MaybeUninit], size: usize, index: usize, } -impl<'a, T> Iterator for StaticVectorMutableIterator<'a, T> { +impl<'a, T> Iterator for IterMut<'a, T> { type Item = &'a mut T; fn next(&mut self) -> Option { From 1224e8dc962e2b293a565c6a9a92290085e0485a Mon Sep 17 00:00:00 2001 From: Andrei Avram <6795248+andreiavrammsd@users.noreply.github.com> Date: Sun, 18 May 2025 11:35:35 +0300 Subject: [PATCH 4/6] Add constructor to iterators --- src/lib.rs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ed462dd..d4500d6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -159,13 +159,13 @@ impl Vec { /// Returns an iterator over immutable references to the elements in the vector. #[inline(always)] pub fn iter(&self) -> Iter { - Iter { data: &self.data, size: self.length, index: 0 } + Iter::new(&self.data, self.length) } /// Returns an iterator over mutable references to the elements in the vector. #[inline(always)] pub fn iter_mut(&mut self) -> IterMut { - IterMut { data: &mut self.data, size: self.length, index: 0 } + IterMut::new(&mut self.data, self.length) } fn drop(&mut self, from: usize, to: usize) { @@ -193,6 +193,14 @@ pub struct Iter<'a, T> { index: usize, } +impl<'a, T> Iter<'a, T> { + /// Creates immutable iterator. + #[inline(always)] + pub fn new(data: &'a [MaybeUninit], size: usize) -> Self { + Self { data, size, index: 0 } + } +} + impl<'a, T> Iterator for Iter<'a, T> { type Item = &'a T; @@ -217,6 +225,14 @@ pub struct IterMut<'a, T> { index: usize, } +impl<'a, T> IterMut<'a, T> { + /// Creates mutable iterator. + #[inline(always)] + pub fn new(data: &'a mut [MaybeUninit], size: usize) -> Self { + Self { data, size, index: 0 } + } +} + impl<'a, T> Iterator for IterMut<'a, T> { type Item = &'a mut T; From 5b1acfbd0126e617ff59c0db39a49d646c809ad0 Mon Sep 17 00:00:00 2001 From: Andrei Avram <6795248+andreiavrammsd@users.noreply.github.com> Date: Sun, 18 May 2025 11:43:19 +0300 Subject: [PATCH 5/6] Enforce coverage failure --- .github/workflows/ci.yml | 1 - codecov.yml | 8 ++++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 619639c..27b5292 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -91,7 +91,6 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} files: lcov.info - fail_ci_if_error: true validate: name: wait for jobs diff --git a/codecov.yml b/codecov.yml index 36bbd8a..afd9925 100644 --- a/codecov.yml +++ b/codecov.yml @@ -2,7 +2,11 @@ coverage: status: patch: default: - threshold: 100 + target: 100% + threshold: 0% + if_ci_failed: error project: default: - threshold: 100 + target: 100% + threshold: 0% + if_ci_failed: error From 96f1e3536d68cd79155ab8f057058d9694a30426 Mon Sep 17 00:00:00 2001 From: Andrei Avram <6795248+andreiavrammsd@users.noreply.github.com> Date: Sun, 18 May 2025 12:13:25 +0300 Subject: [PATCH 6/6] Test error type and message --- src/lib.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index d4500d6..cd6e2a9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -252,7 +252,9 @@ mod tests { use super::*; extern crate std; - use std::{cell::Cell, thread_local}; + use std::{cell::Cell, format, thread_local}; + + fn assert_is_core_error() {} #[test] fn construct() { @@ -281,7 +283,10 @@ mod tests { let mut vec = Vec::::new(); assert!(vec.push(&1).is_ok()); assert!(vec.push(&2).is_ok()); + assert!(matches!(vec.push(&3), Err(CapacityExceededError))); + assert_eq!(format!("{}", vec.push(&3).unwrap_err()), "attempted to push to a full vector"); + assert_is_core_error::(); assert_eq!(vec.get(0).unwrap(), &1); assert_eq!(vec.get(1).unwrap(), &2); @@ -304,6 +309,11 @@ mod tests { assert!(!vec.is_empty()); assert!(matches!(vec.set_len(100), Err(LengthTooLargeError))); + assert_eq!( + format!("{}", vec.set_len(100).unwrap_err()), + "attempted to resize the vector to a length greater than its fixed capacity" + ); + assert_is_core_error::(); vec.clear(); assert_eq!(vec.len(), 0);