From b0adad98489ee19dfa6cd4f2306dad0e8313d287 Mon Sep 17 00:00:00 2001 From: dzmitry-lahoda Date: Thu, 8 May 2025 20:17:25 +0100 Subject: [PATCH 01/15] feat: compile time range validation sanitation to docs clippy docs on usage of prove --- CHANGELOG.md | 2 ++ Cargo.toml | 2 +- README.md | 2 ++ src/bounded_vec.rs | 64 ++++++++++++++++++++++++++++------------------ 4 files changed, 44 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de2e16b..9185acc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] - ReleaseDate +- prevent out of bound construction at compile time + ## [0.7.1] - 2022-08-01 ### Added - fix `Abrbitrary` impl to honor upper(U) and lower(L) bounds; diff --git a/Cargo.toml b/Cargo.toml index aca58e4..5ac0615 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "bounded-vec" version = "0.7.1" license = "CC0-1.0" authors = ["Denys Zadorozhnyi "] -edition = "2018" +edition = "2021" description = "Non-empty rust Vec wrapper with type guarantees on lower and upper bounds for items quantity." repository = "https://github.com/ergoplatform/bounded-vec" diff --git a/README.md b/README.md index b6731a9..42a27c2 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ ## bounded-vec `BoundedVec` - Non-empty rust `std::vec::Vec` wrapper with type guarantees on lower(`L`) and upper(`U`) bounds for items quantity. Inspired by [vec1](https://github.com/rustonaut/vec1). +This crate is `#![no_std]` compatible with `alloc`. ## Example @@ -21,6 +22,7 @@ assert_eq!(data, [2u8,4].into()); ## Crate features - optional(non-default) `serde` feature that adds serialization to `BoundedVec`. +- optional(non-default) `schema` feature that adds JSON schema support via `schemars` (requires `serde`). - optional(non-default) `arbitrary` feature that adds `proptest::Arbitrary` implementation to `BoundedVec`. ## Changelog diff --git a/src/bounded_vec.rs b/src/bounded_vec.rs index 9a31aad..436d096 100644 --- a/src/bounded_vec.rs +++ b/src/bounded_vec.rs @@ -6,20 +6,10 @@ use thiserror::Error; /// Non-empty Vec bounded with minimal (L - lower bound) and maximal (U - upper bound) items quantity #[derive(PartialEq, Eq, Debug, Clone, Hash, PartialOrd, Ord)] -pub struct BoundedVec -// enable when feature(const_evaluatable_checked) is stable -// where -// Assert<{ L > 0 }>: IsTrue, -{ +pub struct BoundedVec { inner: Vec, } -// enum Assert {} - -// trait IsTrue {} - -// impl IsTrue for Assert {} - /// BoundedVec errors #[derive(Error, PartialEq, Eq, Debug, Clone)] pub enum BoundedVecOutOfBounds { @@ -41,18 +31,42 @@ pub enum BoundedVecOutOfBounds { }, } +/// Compile-time proof of valid bounds. Must be consturcted with same bounds to instantiate `BoundedVec`. +#[derive(Clone, Copy)] +pub struct Proof; + +/// Type a compile-time proof of valid bounds +pub const fn prove() -> Proof { + if L == 0 { + panic!("L must be greater than 0") + } + if L > U { + panic!("L must be less than or equal to U") + } + + Proof:: +} + impl BoundedVec { /// Creates new BoundedVec or returns error if items count is out of bounds /// + /// # Parameters + /// + /// * `items` - vector of items within bounds + /// * `proof` - compile-time proof of valid bounds create via `prove::()` function call + /// + /// # Errors + /// + /// * `LowerBoundError` - if `items`` len is less than L (lower bound) + /// * `UpperBoundError` - if `items`` len is more than U (upper bound) + /// /// # Example /// ``` /// use bounded_vec::BoundedVec; - /// let data: BoundedVec<_, 2, 8> = BoundedVec::from_vec(vec![1u8, 2]).unwrap(); + /// use bounded_vec::prove; + /// let data: BoundedVec<_, 2, 8> = BoundedVec::from_vec(vec![1u8, 2], prove::<2, 8>()).unwrap(); /// ``` - pub fn from_vec(items: Vec) -> Result { - // remove when feature(const_evaluatable_checked) is stable - // and this requirement is encoded in type sig - assert!(L > 0); + pub fn from_vec(items: Vec, _: Proof) -> Result { let len = items.len(); if len < L { Err(BoundedVecOutOfBounds::LowerBoundError { @@ -248,7 +262,7 @@ impl BoundedVec { out.push(map_fn(element)?); } #[allow(clippy::unwrap_used)] - Ok(BoundedVec::from_vec(out).unwrap()) + Ok(BoundedVec::from_vec(out, prove::()).unwrap()) } /// Create a new `BoundedVec` by mapping references of `self` elements @@ -280,7 +294,7 @@ impl BoundedVec { out.push(map_fn(element)?); } #[allow(clippy::unwrap_used)] - Ok(BoundedVec::from_vec(out).unwrap()) + Ok(BoundedVec::from_vec(out, prove::()).unwrap()) } /// Returns a reference for an element at index or `None` if out of bounds @@ -341,7 +355,7 @@ impl BoundedVec { if v.is_empty() { Ok(None) } else { - Ok(Some(BoundedVec::from_vec(v)?)) + Ok(Some(BoundedVec::from_vec(v, prove::())?)) } } } @@ -353,7 +367,7 @@ impl TryFrom> for BoundedVec type Error = BoundedVecOutOfBounds; fn try_from(value: Vec) -> Result { - BoundedVec::from_vec(value) + BoundedVec::from_vec(value, prove::()) } } @@ -452,7 +466,7 @@ mod arbitrary { fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { vec(any::(), L..=U) - .prop_map(|items| BoundedVec::from_vec(items).unwrap()) + .prop_map(|items| BoundedVec::from_vec(items, prove::()).unwrap()) .boxed() } } @@ -538,10 +552,10 @@ mod tests { #[test] fn from_vec() { - assert!(BoundedVec::::from_vec(vec![1, 2]).is_ok()); - assert!(BoundedVec::::from_vec(vec![]).is_err()); - assert!(BoundedVec::::from_vec(vec![1, 2]).is_err()); - assert!(BoundedVec::::from_vec(vec![1, 2, 3]).is_err()); + assert!(BoundedVec::::from_vec(vec![1, 2], prove::<2, 8>()).is_ok()); + assert!(BoundedVec::::from_vec(vec![], prove::<2, 8>()).is_err()); + assert!(BoundedVec::::from_vec(vec![1, 2], prove::<3, 8>()).is_err()); + assert!(BoundedVec::::from_vec(vec![1, 2, 3], prove::<1, 2>()).is_err()); } #[test] From b1909ee2ac5487a29fc712da46c185dd13347cd7 Mon Sep 17 00:00:00 2001 From: dzmitry-lahoda Date: Fri, 9 May 2025 10:44:03 +0100 Subject: [PATCH 02/15] support for empty --- src/bounded_vec.rs | 41 +++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/src/bounded_vec.rs b/src/bounded_vec.rs index 436d096..4b2ce2a 100644 --- a/src/bounded_vec.rs +++ b/src/bounded_vec.rs @@ -6,7 +6,7 @@ use thiserror::Error; /// Non-empty Vec bounded with minimal (L - lower bound) and maximal (U - upper bound) items quantity #[derive(PartialEq, Eq, Debug, Clone, Hash, PartialOrd, Ord)] -pub struct BoundedVec { +pub struct BoundedVec { inner: Vec, } @@ -33,10 +33,13 @@ pub enum BoundedVecOutOfBounds { /// Compile-time proof of valid bounds. Must be consturcted with same bounds to instantiate `BoundedVec`. #[derive(Clone, Copy)] -pub struct Proof; +pub struct Witness; + +trait NonEmpty{} +impl NonEmpty for Witness {} /// Type a compile-time proof of valid bounds -pub const fn prove() -> Proof { +pub const fn prove() -> Witness { if L == 0 { panic!("L must be greater than 0") } @@ -44,16 +47,17 @@ pub const fn prove() -> Proof { panic!("L must be less than or equal to U") } - Proof:: + Witness:: } -impl BoundedVec { +impl BoundedVec +where W: NonEmpty { /// Creates new BoundedVec or returns error if items count is out of bounds /// /// # Parameters /// /// * `items` - vector of items within bounds - /// * `proof` - compile-time proof of valid bounds create via `prove::()` function call + /// * `_witness` - compile-time proof of valid bounds create via `prove::()` function call /// /// # Errors /// @@ -66,7 +70,8 @@ impl BoundedVec { /// use bounded_vec::prove; /// let data: BoundedVec<_, 2, 8> = BoundedVec::from_vec(vec![1u8, 2], prove::<2, 8>()).unwrap(); /// ``` - pub fn from_vec(items: Vec, _: Proof) -> Result { + pub fn from_vec(items: Vec, _witness: W) + -> Result { let len = items.len(); if len < L { Err(BoundedVecOutOfBounds::LowerBoundError { @@ -136,7 +141,7 @@ impl BoundedVec { /// assert_eq!(data.is_empty(), false); /// ``` pub fn is_empty(&self) -> bool { - false + self.inner.is_empty() } /// Extracts a slice containing the entire vector. @@ -196,7 +201,7 @@ impl BoundedVec { /// let data = data.mapped(|x|x*2); /// assert_eq!(data, [2u8,4].into()); /// ``` - pub fn mapped(self, map_fn: F) -> BoundedVec + pub fn mapped(self, map_fn: F) -> BoundedVec where F: FnMut(T) -> N, { @@ -218,7 +223,7 @@ impl BoundedVec { /// let data = data.mapped_ref(|x|x*2); /// assert_eq!(data, [2u8,4].into()); /// ``` - pub fn mapped_ref(&self, map_fn: F) -> BoundedVec + pub fn mapped_ref(&self, map_fn: F) -> BoundedVec where F: FnMut(&T) -> N, { @@ -252,7 +257,7 @@ impl BoundedVec { /// let data: Result, _> = data.try_mapped(|x| Err("failed")); /// assert_eq!(data, Err("failed")); /// ``` - pub fn try_mapped(self, map_fn: F) -> Result, E> + pub fn try_mapped(self, map_fn: F) -> Result, E> where F: FnMut(T) -> Result, { @@ -361,9 +366,9 @@ impl BoundedVec { } /// A non-empty Vec with no effective upper-bound on its length -pub type NonEmptyVec = BoundedVec; +pub type NonEmptyVec = BoundedVec>; -impl TryFrom> for BoundedVec { +impl TryFrom> for BoundedVec { type Error = BoundedVecOutOfBounds; fn try_from(value: Vec) -> Result { @@ -378,8 +383,8 @@ impl From<[T; L]> for BoundedVec { } } -impl From> for Vec { - fn from(v: BoundedVec) -> Self { +impl From> for Vec { + fn from(v: BoundedVec) -> Self { v.inner } } @@ -423,13 +428,13 @@ impl AsRef<[T]> for BoundedVec { } } -impl AsMut> for BoundedVec { +impl AsMut> for BoundedVec { fn as_mut(&mut self) -> &mut Vec { self.inner.as_mut() } } -impl AsMut<[T]> for BoundedVec { +impl AsMut<[T]> for BoundedVec { fn as_mut(&mut self) -> &mut [T] { self.inner.as_mut() } @@ -441,7 +446,7 @@ pub trait OptBoundedVecToVec { fn to_vec(self) -> Vec; } -impl OptBoundedVecToVec for Option> { +impl OptBoundedVecToVec for Option> { fn to_vec(self) -> Vec { self.map(|bv| bv.into()).unwrap_or_default() } From 905e0ebc9a82ebf811d4d3acb992a58927ea8145 Mon Sep 17 00:00:00 2001 From: dzmitry-lahoda Date: Fri, 9 May 2025 11:09:50 +0100 Subject: [PATCH 03/15] non empty typing --- CHANGELOG.md | 2 +- src/bounded_vec.rs | 230 +++++++++++++++++++++++++-------------------- 2 files changed, 128 insertions(+), 104 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9185acc..c2c603f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] - ReleaseDate -- prevent out of bound construction at compile time +- prevent out of bound construction and define empty vs nonempty at compile time ## [0.7.1] - 2022-08-01 ### Added diff --git a/src/bounded_vec.rs b/src/bounded_vec.rs index 4b2ce2a..005dfa9 100644 --- a/src/bounded_vec.rs +++ b/src/bounded_vec.rs @@ -6,8 +6,9 @@ use thiserror::Error; /// Non-empty Vec bounded with minimal (L - lower bound) and maximal (U - upper bound) items quantity #[derive(PartialEq, Eq, Debug, Clone, Hash, PartialOrd, Ord)] -pub struct BoundedVec { +pub struct BoundedVec> { inner: Vec, + _marker: core::marker::PhantomData, } /// BoundedVec errors @@ -31,15 +32,18 @@ pub enum BoundedVecOutOfBounds { }, } +// NOTE: we can have proves if needed for some cases like 8/16/32/64 upper bound, so can make memory and serde more compile safe and efficient + /// Compile-time proof of valid bounds. Must be consturcted with same bounds to instantiate `BoundedVec`. #[derive(Clone, Copy)] -pub struct Witness; +pub struct NonEmptyWitness; -trait NonEmpty{} -impl NonEmpty for Witness {} +/// Possibly empty vector with upper bound. +#[derive(Clone, Copy)] +pub struct EmptyWitness; /// Type a compile-time proof of valid bounds -pub const fn prove() -> Witness { +pub const fn non_empty() -> NonEmptyWitness { if L == 0 { panic!("L must be greater than 0") } @@ -47,17 +51,73 @@ pub const fn prove() -> Witness { panic!("L must be less than or equal to U") } - Witness:: + NonEmptyWitness:: +} + +pub const fn empty() -> EmptyWitness { + EmptyWitness:: +} + +impl BoundedVec> { + pub fn first(&self) -> Option<&T> { + self.inner.first() + } + + /// Always returns `false` (cannot be empty) + /// + /// # Example + /// ``` + /// use bounded_vec::BoundedVec; + /// use std::convert::TryInto; + /// + /// let data: BoundedVec<_, 2, 8> = vec![1u8, 2].try_into().unwrap(); + /// assert_eq!(data.is_empty(), false); + /// ``` + pub fn is_empty(&self) -> bool { + self.inner.is_empty() + } + + pub fn last(&self) -> Option<&T> { + self.inner.last() + } } -impl BoundedVec -where W: NonEmpty { +/// Part which works for all witnesses +impl BoundedVec { + /// Returns a reference to underlying `Vec`` + /// + /// # Example + /// ``` + /// use bounded_vec::BoundedVec; + /// use std::convert::TryInto; + /// + /// let data: BoundedVec<_, 2, 8> = vec![1u8, 2].try_into().unwrap(); + /// assert_eq!(data.as_vec(), &vec![1u8,2]); + /// ``` + pub fn as_vec(&self) -> &Vec { + &self.inner + } + + /// Returns an underlying `Vec`` + /// + /// # Example + /// ``` + /// use bounded_vec::BoundedVec; + /// use std::convert::TryInto; + /// + /// let data: BoundedVec<_, 2, 8> = vec![1u8, 2].try_into().unwrap(); + /// assert_eq!(data.to_vec(), vec![1u8,2]); + /// ``` + pub fn to_vec(self) -> Vec { + self.inner.into() + } + /// Creates new BoundedVec or returns error if items count is out of bounds /// /// # Parameters /// /// * `items` - vector of items within bounds - /// * `_witness` - compile-time proof of valid bounds create via `prove::()` function call + /// * `_witness` - compile-time proof of valid bounds create via `non_empty::()` function call /// /// # Errors /// @@ -67,10 +127,10 @@ where W: NonEmpty { /// # Example /// ``` /// use bounded_vec::BoundedVec; - /// use bounded_vec::prove; - /// let data: BoundedVec<_, 2, 8> = BoundedVec::from_vec(vec![1u8, 2], prove::<2, 8>()).unwrap(); + /// use bounded_vec::non_empty; + /// let data: BoundedVec<_, 2, 8> = BoundedVec::from_vec(vec![1u8, 2], non_empty::<2, 8>()).unwrap(); /// ``` - pub fn from_vec(items: Vec, _witness: W) + pub fn from_vec(items: Vec, _witness: NonEmptyWitness) -> Result { let len = items.len(); if len < L { @@ -84,11 +144,11 @@ where W: NonEmpty { got: len, }) } else { - Ok(BoundedVec { inner: items }) + Ok(BoundedVec { inner: items, _marker: <_>::default(), }) } } - /// Returns a reference to underlying `Vec`` + /// Extracts a slice containing the entire vector. /// /// # Example /// ``` @@ -96,26 +156,40 @@ where W: NonEmpty { /// use std::convert::TryInto; /// /// let data: BoundedVec<_, 2, 8> = vec![1u8, 2].try_into().unwrap(); - /// assert_eq!(data.as_vec(), &vec![1u8,2]); + /// assert_eq!(data.as_slice(), &[1u8,2]); /// ``` - pub fn as_vec(&self) -> &Vec { - &self.inner + pub fn as_slice(&self) -> &[T] { + self.inner.as_slice() } - /// Returns an underlying `Vec`` + + /// Returns a reference for an element at index or `None` if out of bounds /// /// # Example + /// /// ``` /// use bounded_vec::BoundedVec; - /// use std::convert::TryInto; - /// - /// let data: BoundedVec<_, 2, 8> = vec![1u8, 2].try_into().unwrap(); - /// assert_eq!(data.to_vec(), vec![1u8,2]); + /// let data: BoundedVec = [1u8,2].into(); + /// let elem = *data.get(1).unwrap(); + /// assert_eq!(elem, 2); /// ``` - pub fn to_vec(self) -> Vec { - self.into() + pub fn get(&self, index: usize) -> Option<&T> { + self.inner.get(index) } + /// Returns an iterator + pub fn iter(&self) -> Iter { + self.inner.iter() + } + + /// Returns an iterator that allows to modify each value + pub fn iter_mut(&mut self) -> IterMut { + self.inner.iter_mut() + } + +} + +impl BoundedVec> { /// Returns the number of elements in the vector /// /// # Example @@ -130,34 +204,6 @@ where W: NonEmpty { self.inner.len() } - /// Always returns `false` (cannot be empty) - /// - /// # Example - /// ``` - /// use bounded_vec::BoundedVec; - /// use std::convert::TryInto; - /// - /// let data: BoundedVec<_, 2, 8> = vec![1u8, 2].try_into().unwrap(); - /// assert_eq!(data.is_empty(), false); - /// ``` - pub fn is_empty(&self) -> bool { - self.inner.is_empty() - } - - /// Extracts a slice containing the entire vector. - /// - /// # Example - /// ``` - /// use bounded_vec::BoundedVec; - /// use std::convert::TryInto; - /// - /// let data: BoundedVec<_, 2, 8> = vec![1u8, 2].try_into().unwrap(); - /// assert_eq!(data.as_slice(), &[1u8,2]); - /// ``` - pub fn as_slice(&self) -> &[T] { - self.inner.as_slice() - } - /// Returns the first element of non-empty Vec /// /// # Example @@ -201,12 +247,13 @@ where W: NonEmpty { /// let data = data.mapped(|x|x*2); /// assert_eq!(data, [2u8,4].into()); /// ``` - pub fn mapped(self, map_fn: F) -> BoundedVec + pub fn mapped(self, map_fn: F) -> BoundedVec> where F: FnMut(T) -> N, { BoundedVec { inner: self.inner.into_iter().map(map_fn).collect::>(), + _marker: <_>::default(), } } @@ -223,12 +270,13 @@ where W: NonEmpty { /// let data = data.mapped_ref(|x|x*2); /// assert_eq!(data, [2u8,4].into()); /// ``` - pub fn mapped_ref(&self, map_fn: F) -> BoundedVec + pub fn mapped_ref(&self, map_fn: F) -> BoundedVec> where F: FnMut(&T) -> N, { BoundedVec { inner: self.inner.iter().map(map_fn).collect::>(), + _marker: <_>::default(), } } @@ -257,7 +305,7 @@ where W: NonEmpty { /// let data: Result, _> = data.try_mapped(|x| Err("failed")); /// assert_eq!(data, Err("failed")); /// ``` - pub fn try_mapped(self, map_fn: F) -> Result, E> + pub fn try_mapped(self, map_fn: F) -> Result>, E> where F: FnMut(T) -> Result, { @@ -267,7 +315,7 @@ where W: NonEmpty { out.push(map_fn(element)?); } #[allow(clippy::unwrap_used)] - Ok(BoundedVec::from_vec(out, prove::()).unwrap()) + Ok(BoundedVec::from_vec(out, non_empty::()).unwrap()) } /// Create a new `BoundedVec` by mapping references of `self` elements @@ -289,7 +337,7 @@ where W: NonEmpty { /// let data: Result, _> = data.try_mapped_ref(|x| Err("failed")); /// assert_eq!(data, Err("failed")); /// ``` - pub fn try_mapped_ref(&self, map_fn: F) -> Result, E> + pub fn try_mapped_ref(&self, map_fn: F) -> Result>, E> where F: FnMut(&T) -> Result, { @@ -299,31 +347,7 @@ where W: NonEmpty { out.push(map_fn(element)?); } #[allow(clippy::unwrap_used)] - Ok(BoundedVec::from_vec(out, prove::()).unwrap()) - } - - /// Returns a reference for an element at index or `None` if out of bounds - /// - /// # Example - /// - /// ``` - /// use bounded_vec::BoundedVec; - /// let data: BoundedVec = [1u8,2].into(); - /// let elem = *data.get(1).unwrap(); - /// assert_eq!(elem, 2); - /// ``` - pub fn get(&self, index: usize) -> Option<&T> { - self.inner.get(index) - } - - /// Returns an iterator - pub fn iter(&self) -> Iter { - self.inner.iter() - } - - /// Returns an iterator that allows to modify each value - pub fn iter_mut(&mut self) -> IterMut { - self.inner.iter_mut() + Ok(BoundedVec::from_vec(out, non_empty::()).unwrap()) } /// Returns the last and all the rest of the elements @@ -333,7 +357,7 @@ where W: NonEmpty { } /// Return a new BoundedVec with indices included - pub fn enumerated(self) -> BoundedVec<(usize, T), L, U> { + pub fn enumerated(self) -> BoundedVec<(usize, T), L, U, NonEmptyWitness> { #[allow(clippy::unwrap_used)] self.inner .into_iter() @@ -356,40 +380,40 @@ where W: NonEmpty { /// assert!(opt_bv_some.is_some()); /// assert_eq!(opt_bv_some.to_vec(), vec![0u8, 2]); /// ``` - pub fn opt_empty_vec(v: Vec) -> Result>, BoundedVecOutOfBounds> { + pub fn opt_empty_vec(v: Vec) -> Result>>, BoundedVecOutOfBounds> { if v.is_empty() { Ok(None) } else { - Ok(Some(BoundedVec::from_vec(v, prove::())?)) + Ok(Some(BoundedVec::from_vec(v, non_empty::())?)) } } } /// A non-empty Vec with no effective upper-bound on its length -pub type NonEmptyVec = BoundedVec>; +pub type NonEmptyVec = BoundedVec>; -impl TryFrom> for BoundedVec { +impl TryFrom> for BoundedVec> { type Error = BoundedVecOutOfBounds; fn try_from(value: Vec) -> Result { - BoundedVec::from_vec(value, prove::()) + BoundedVec::from_vec(value, non_empty::()) } } // when feature(const_evaluatable_checked) is stable cover all array sizes (L..=U) -impl From<[T; L]> for BoundedVec { +impl From<[T; L]> for BoundedVec> { fn from(arr: [T; L]) -> Self { - BoundedVec { inner: arr.into() } + BoundedVec { inner: arr.into(), _marker: <_>::default() } } } -impl From> for Vec { - fn from(v: BoundedVec) -> Self { +impl From>> for Vec { + fn from(v: BoundedVec>) -> Self { v.inner } } -impl IntoIterator for BoundedVec { +impl IntoIterator for BoundedVec { type Item = T; type IntoIter = vec::IntoIter; @@ -398,7 +422,7 @@ impl IntoIterator for BoundedVec { } } -impl<'a, T, const L: usize, const U: usize> IntoIterator for &'a BoundedVec { +impl<'a, T, const L: usize, const U: usize, W> IntoIterator for &'a BoundedVec { type Item = &'a T; type IntoIter = core::slice::Iter<'a, T>; @@ -407,7 +431,7 @@ impl<'a, T, const L: usize, const U: usize> IntoIterator for &'a BoundedVec IntoIterator for &'a mut BoundedVec { +impl<'a, T, const L: usize, const U: usize, W> IntoIterator for &'a mut BoundedVec { type Item = &'a mut T; type IntoIter = core::slice::IterMut<'a, T>; @@ -416,13 +440,13 @@ impl<'a, T, const L: usize, const U: usize> IntoIterator for &'a mut BoundedVec< } } -impl AsRef> for BoundedVec { +impl AsRef> for BoundedVec { fn as_ref(&self) -> &Vec { &self.inner } } -impl AsRef<[T]> for BoundedVec { +impl AsRef<[T]> for BoundedVec { fn as_ref(&self) -> &[T] { self.inner.as_ref() } @@ -446,7 +470,7 @@ pub trait OptBoundedVecToVec { fn to_vec(self) -> Vec; } -impl OptBoundedVecToVec for Option> { +impl OptBoundedVecToVec for Option>> { fn to_vec(self) -> Vec { self.map(|bv| bv.into()).unwrap_or_default() } @@ -471,7 +495,7 @@ mod arbitrary { fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { vec(any::(), L..=U) - .prop_map(|items| BoundedVec::from_vec(items, prove::()).unwrap()) + .prop_map(|items| BoundedVec::from_vec(items, non_empty::()).unwrap()) .boxed() } } @@ -557,10 +581,10 @@ mod tests { #[test] fn from_vec() { - assert!(BoundedVec::::from_vec(vec![1, 2], prove::<2, 8>()).is_ok()); - assert!(BoundedVec::::from_vec(vec![], prove::<2, 8>()).is_err()); - assert!(BoundedVec::::from_vec(vec![1, 2], prove::<3, 8>()).is_err()); - assert!(BoundedVec::::from_vec(vec![1, 2, 3], prove::<1, 2>()).is_err()); + assert!(BoundedVec::::from_vec(vec![1, 2], non_empty::<2, 8>()).is_ok()); + assert!(BoundedVec::::from_vec(vec![], non_empty::<2, 8>()).is_err()); + assert!(BoundedVec::::from_vec(vec![1, 2], non_empty::<3, 8>()).is_err()); + assert!(BoundedVec::::from_vec(vec![1, 2, 3], non_empty::<1, 2>()).is_err()); } #[test] From 641fe190550074fe91d0720ccf5ed2f37f212b8c Mon Sep 17 00:00:00 2001 From: dzmitry-lahoda Date: Fri, 9 May 2025 11:12:23 +0100 Subject: [PATCH 04/15] fixes --- src/bounded_vec.rs | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/src/bounded_vec.rs b/src/bounded_vec.rs index 005dfa9..628d7bd 100644 --- a/src/bounded_vec.rs +++ b/src/bounded_vec.rs @@ -35,11 +35,11 @@ pub enum BoundedVecOutOfBounds { // NOTE: we can have proves if needed for some cases like 8/16/32/64 upper bound, so can make memory and serde more compile safe and efficient /// Compile-time proof of valid bounds. Must be consturcted with same bounds to instantiate `BoundedVec`. -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct NonEmptyWitness; /// Possibly empty vector with upper bound. -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct EmptyWitness; /// Type a compile-time proof of valid bounds @@ -54,29 +54,50 @@ pub const fn non_empty() -> NonEmptyWitness } +/// Type a compile-time proof for possibly empty vector with upper bound pub const fn empty() -> EmptyWitness { EmptyWitness:: } impl BoundedVec> { + /// Returns the first element of the vector, or `None` if it is empty + /// + /// # Example + /// ``` + /// use bounded_vec::BoundedVec; + /// use bounded_vec::empty; + /// + /// let data: BoundedVec<_, 0, 8, _> = BoundedVec::from_vec(vec![1u8, 2], empty::<8>()).unwrap(); + /// assert_eq!(data.first(), Some(&1u8)); + /// ``` pub fn first(&self) -> Option<&T> { self.inner.first() } - /// Always returns `false` (cannot be empty) + /// Returns `true` if the vector contains no elements /// /// # Example /// ``` /// use bounded_vec::BoundedVec; - /// use std::convert::TryInto; + /// use bounded_vec::empty; /// - /// let data: BoundedVec<_, 2, 8> = vec![1u8, 2].try_into().unwrap(); + /// let data: BoundedVec<_, 0, 8, _> = BoundedVec::from_vec(vec![1u8, 2], empty::<8>()).unwrap(); /// assert_eq!(data.is_empty(), false); /// ``` pub fn is_empty(&self) -> bool { self.inner.is_empty() } + /// Returns the last element of the vector, or `None` if it is empty + /// + /// # Example + /// ``` + /// use bounded_vec::BoundedVec; + /// use bounded_vec::empty; + /// + /// let data: BoundedVec<_, 0, 8, _> = BoundedVec::from_vec(vec![1u8, 2], empty::<8>()).unwrap(); + /// assert_eq!(data.last(), Some(&2u8)); + /// ``` pub fn last(&self) -> Option<&T> { self.inner.last() } @@ -589,7 +610,7 @@ mod tests { #[test] fn is_empty() { - let data: BoundedVec<_, 2, 8> = vec![1u8, 2].try_into().unwrap(); + let data: BoundedVec<_, 0, 8, EmptyWitness::<8>> = vec![1u8, 2].try_into().unwrap(); assert!(!data.is_empty()); } From 3d03961adb246c895c9e4611b7dc7c387a3906d5 Mon Sep 17 00:00:00 2001 From: dzmitry-lahoda Date: Fri, 9 May 2025 11:19:55 +0100 Subject: [PATCH 05/15] right --- src/bounded_vec.rs | 127 +++++++++++++++++++++++++++++---------------- 1 file changed, 83 insertions(+), 44 deletions(-) diff --git a/src/bounded_vec.rs b/src/bounded_vec.rs index 628d7bd..3788226 100644 --- a/src/bounded_vec.rs +++ b/src/bounded_vec.rs @@ -59,15 +59,49 @@ pub const fn empty() -> EmptyWitness { EmptyWitness:: } -impl BoundedVec> { +impl BoundedVec> { + + /// Creates new BoundedVec or returns error if items count is out of bounds + /// + /// # Parameters + /// + /// * `items` - vector of items within bounds + /// * `_witness` - compile-time proof of valid bounds create via `non_empty::()` function call + /// + /// # Errors + /// + /// * `LowerBoundError` - if `items`` len is less than L (lower bound) + /// * `UpperBoundError` - if `items`` len is more than U (upper bound) + /// + /// # Example + /// ``` + /// use bounded_vec::BoundedVec; + /// use bounded_vec::non_empty; + /// let data: BoundedVec<_, 2, 8> = BoundedVec::from_vec(vec![1u8, 2], non_empty::<2, 8>()).unwrap(); + /// ``` + pub fn from_vec(items: Vec, _witness: EmptyWitness) + -> Result { + let len = items.len(); + if len > U { + Err(BoundedVecOutOfBounds::UpperBoundError { + upper_bound: U, + got: len, + }) + } else { + Ok(BoundedVec { inner: items, _marker: <_>::default(), }) + } + } + /// Returns the first element of the vector, or `None` if it is empty /// /// # Example /// ``` /// use bounded_vec::BoundedVec; /// use bounded_vec::empty; + /// use bounded_vec::EmptyWitness; + /// use std::convert::TryInto; /// - /// let data: BoundedVec<_, 0, 8, _> = BoundedVec::from_vec(vec![1u8, 2], empty::<8>()).unwrap(); + /// let data: BoundedVec> = vec![1u8, 2].try_into().unwrap(); /// assert_eq!(data.first(), Some(&1u8)); /// ``` pub fn first(&self) -> Option<&T> { @@ -80,8 +114,10 @@ impl BoundedVec> { /// ``` /// use bounded_vec::BoundedVec; /// use bounded_vec::empty; + /// use bounded_vec::EmptyWitness; + /// use std::convert::TryInto; /// - /// let data: BoundedVec<_, 0, 8, _> = BoundedVec::from_vec(vec![1u8, 2], empty::<8>()).unwrap(); + /// let data: BoundedVec> = vec![1u8, 2].try_into().unwrap(); /// assert_eq!(data.is_empty(), false); /// ``` pub fn is_empty(&self) -> bool { @@ -94,8 +130,10 @@ impl BoundedVec> { /// ``` /// use bounded_vec::BoundedVec; /// use bounded_vec::empty; + /// use bounded_vec::EmptyWitness; + /// use std::convert::TryInto; /// - /// let data: BoundedVec<_, 0, 8, _> = BoundedVec::from_vec(vec![1u8, 2], empty::<8>()).unwrap(); + /// let data: BoundedVec> = vec![1u8, 2].try_into().unwrap(); /// assert_eq!(data.last(), Some(&2u8)); /// ``` pub fn last(&self) -> Option<&T> { @@ -133,42 +171,6 @@ impl BoundedVec { self.inner.into() } - /// Creates new BoundedVec or returns error if items count is out of bounds - /// - /// # Parameters - /// - /// * `items` - vector of items within bounds - /// * `_witness` - compile-time proof of valid bounds create via `non_empty::()` function call - /// - /// # Errors - /// - /// * `LowerBoundError` - if `items`` len is less than L (lower bound) - /// * `UpperBoundError` - if `items`` len is more than U (upper bound) - /// - /// # Example - /// ``` - /// use bounded_vec::BoundedVec; - /// use bounded_vec::non_empty; - /// let data: BoundedVec<_, 2, 8> = BoundedVec::from_vec(vec![1u8, 2], non_empty::<2, 8>()).unwrap(); - /// ``` - pub fn from_vec(items: Vec, _witness: NonEmptyWitness) - -> Result { - let len = items.len(); - if len < L { - Err(BoundedVecOutOfBounds::LowerBoundError { - lower_bound: L, - got: len, - }) - } else if len > U { - Err(BoundedVecOutOfBounds::UpperBoundError { - upper_bound: U, - got: len, - }) - } else { - Ok(BoundedVec { inner: items, _marker: <_>::default(), }) - } - } - /// Extracts a slice containing the entire vector. /// /// # Example @@ -211,6 +213,43 @@ impl BoundedVec { } impl BoundedVec> { + + /// Creates new BoundedVec or returns error if items count is out of bounds + /// + /// # Parameters + /// + /// * `items` - vector of items within bounds + /// * `_witness` - compile-time proof of valid bounds create via `non_empty::()` function call + /// + /// # Errors + /// + /// * `LowerBoundError` - if `items`` len is less than L (lower bound) + /// * `UpperBoundError` - if `items`` len is more than U (upper bound) + /// + /// # Example + /// ``` + /// use bounded_vec::BoundedVec; + /// use bounded_vec::non_empty; + /// let data: BoundedVec<_, 2, 8> = BoundedVec::from_vec(vec![1u8, 2], non_empty::<2, 8>()).unwrap(); + /// ``` + pub fn from_vec(items: Vec, _witness: NonEmptyWitness) + -> Result { + let len = items.len(); + if len < L { + Err(BoundedVecOutOfBounds::LowerBoundError { + lower_bound: L, + got: len, + }) + } else if len > U { + Err(BoundedVecOutOfBounds::UpperBoundError { + upper_bound: U, + got: len, + }) + } else { + Ok(BoundedVec { inner: items, _marker: <_>::default(), }) + } + } + /// Returns the number of elements in the vector /// /// # Example @@ -336,7 +375,7 @@ impl BoundedVec()).unwrap()) + Ok(BoundedVec::>::from_vec(out, non_empty::()).unwrap()) } /// Create a new `BoundedVec` by mapping references of `self` elements @@ -368,7 +407,7 @@ impl BoundedVec()).unwrap()) + Ok(BoundedVec::>::from_vec(out, non_empty::()).unwrap()) } /// Returns the last and all the rest of the elements @@ -405,7 +444,7 @@ impl BoundedVec())?)) + Ok(Some(BoundedVec::>::from_vec(v, non_empty::())?)) } } } @@ -417,7 +456,7 @@ impl TryFrom> for BoundedVec) -> Result { - BoundedVec::from_vec(value, non_empty::()) + BoundedVec::>::from_vec(value, non_empty::()) } } From 48b87de7cb6b1d80883d6fd6b7ac0c39f8dc2b2a Mon Sep 17 00:00:00 2001 From: dzmitry-lahoda Date: Fri, 9 May 2025 11:25:13 +0100 Subject: [PATCH 06/15] compile --- src/bounded_vec.rs | 134 +++++++++++++++++++++++++++++++-------------- 1 file changed, 94 insertions(+), 40 deletions(-) diff --git a/src/bounded_vec.rs b/src/bounded_vec.rs index 3788226..eaf7c84 100644 --- a/src/bounded_vec.rs +++ b/src/bounded_vec.rs @@ -59,36 +59,41 @@ pub const fn empty() -> EmptyWitness { EmptyWitness:: } -impl BoundedVec> { - +impl BoundedVec> { /// Creates new BoundedVec or returns error if items count is out of bounds /// /// # Parameters /// /// * `items` - vector of items within bounds - /// * `_witness` - compile-time proof of valid bounds create via `non_empty::()` function call + /// * `_witness` - compile-time proof of valid bounds create via `empty::()` function call /// /// # Errors /// - /// * `LowerBoundError` - if `items`` len is less than L (lower bound) /// * `UpperBoundError` - if `items`` len is more than U (upper bound) /// /// # Example /// ``` /// use bounded_vec::BoundedVec; - /// use bounded_vec::non_empty; - /// let data: BoundedVec<_, 2, 8> = BoundedVec::from_vec(vec![1u8, 2], non_empty::<2, 8>()).unwrap(); + /// use bounded_vec::empty; + /// use bounded_vec::EmptyWitness; + /// let data: BoundedVec<_, 0, 8, EmptyWitness<8>> = + /// BoundedVec::<_, 0, 8, EmptyWitness<8>>::from_vec(vec![1u8, 2], empty::<8>()).unwrap(); /// ``` - pub fn from_vec(items: Vec, _witness: EmptyWitness) - -> Result { + pub fn from_vec( + items: Vec, + _witness: EmptyWitness, + ) -> Result { let len = items.len(); - if len > U { + if len > U { Err(BoundedVecOutOfBounds::UpperBoundError { upper_bound: U, got: len, }) } else { - Ok(BoundedVec { inner: items, _marker: <_>::default(), }) + Ok(BoundedVec { + inner: items, + _marker: core::marker::PhantomData, + }) } } @@ -100,7 +105,7 @@ impl BoundedVec> { /// use bounded_vec::empty; /// use bounded_vec::EmptyWitness; /// use std::convert::TryInto; - /// + /// /// let data: BoundedVec> = vec![1u8, 2].try_into().unwrap(); /// assert_eq!(data.first(), Some(&1u8)); /// ``` @@ -122,7 +127,7 @@ impl BoundedVec> { /// ``` pub fn is_empty(&self) -> bool { self.inner.is_empty() - } + } /// Returns the last element of the vector, or `None` if it is empty /// @@ -132,7 +137,7 @@ impl BoundedVec> { /// use bounded_vec::empty; /// use bounded_vec::EmptyWitness; /// use std::convert::TryInto; - /// + /// /// let data: BoundedVec> = vec![1u8, 2].try_into().unwrap(); /// assert_eq!(data.last(), Some(&2u8)); /// ``` @@ -168,7 +173,7 @@ impl BoundedVec { /// assert_eq!(data.to_vec(), vec![1u8,2]); /// ``` pub fn to_vec(self) -> Vec { - self.inner.into() + self.inner } /// Extracts a slice containing the entire vector. @@ -185,7 +190,6 @@ impl BoundedVec { self.inner.as_slice() } - /// Returns a reference for an element at index or `None` if out of bounds /// /// # Example @@ -209,11 +213,9 @@ impl BoundedVec { pub fn iter_mut(&mut self) -> IterMut { self.inner.iter_mut() } - } impl BoundedVec> { - /// Creates new BoundedVec or returns error if items count is out of bounds /// /// # Parameters @@ -230,10 +232,14 @@ impl BoundedVec = BoundedVec::from_vec(vec![1u8, 2], non_empty::<2, 8>()).unwrap(); - /// ``` - pub fn from_vec(items: Vec, _witness: NonEmptyWitness) - -> Result { + /// use bounded_vec::NonEmptyWitness; + /// let data: BoundedVec<_, 2, 8, NonEmptyWitness<2, 8>> = + /// BoundedVec::<_, 2, 8, NonEmptyWitness<2, 8>>::from_vec(vec![1u8, 2], non_empty::<2, 8>()).unwrap(); + /// ``` + pub fn from_vec( + items: Vec, + _witness: NonEmptyWitness, + ) -> Result { let len = items.len(); if len < L { Err(BoundedVecOutOfBounds::LowerBoundError { @@ -246,7 +252,10 @@ impl BoundedVec::default(), }) + Ok(BoundedVec { + inner: items, + _marker: core::marker::PhantomData, + }) } } @@ -313,7 +322,7 @@ impl BoundedVec>(), - _marker: <_>::default(), + _marker: core::marker::PhantomData, } } @@ -336,7 +345,7 @@ impl BoundedVec>(), - _marker: <_>::default(), + _marker: core::marker::PhantomData, } } @@ -365,7 +374,10 @@ impl BoundedVec, _> = data.try_mapped(|x| Err("failed")); /// assert_eq!(data, Err("failed")); /// ``` - pub fn try_mapped(self, map_fn: F) -> Result>, E> + pub fn try_mapped( + self, + map_fn: F, + ) -> Result>, E> where F: FnMut(T) -> Result, { @@ -375,7 +387,10 @@ impl BoundedVec>::from_vec(out, non_empty::()).unwrap()) + Ok( + BoundedVec::>::from_vec(out, non_empty::()) + .unwrap(), + ) } /// Create a new `BoundedVec` by mapping references of `self` elements @@ -397,7 +412,10 @@ impl BoundedVec, _> = data.try_mapped_ref(|x| Err("failed")); /// assert_eq!(data, Err("failed")); /// ``` - pub fn try_mapped_ref(&self, map_fn: F) -> Result>, E> + pub fn try_mapped_ref( + &self, + map_fn: F, + ) -> Result>, E> where F: FnMut(&T) -> Result, { @@ -407,7 +425,10 @@ impl BoundedVec>::from_vec(out, non_empty::()).unwrap()) + Ok( + BoundedVec::>::from_vec(out, non_empty::()) + .unwrap(), + ) } /// Returns the last and all the rest of the elements @@ -440,11 +461,15 @@ impl BoundedVec) -> Result>>, BoundedVecOutOfBounds> { + pub fn opt_empty_vec( + v: Vec, + ) -> Result>>, BoundedVecOutOfBounds> { if v.is_empty() { Ok(None) } else { - Ok(Some(BoundedVec::>::from_vec(v, non_empty::())?)) + Ok(Some( + BoundedVec::>::from_vec(v, non_empty::())?, + )) } } } @@ -452,22 +477,39 @@ impl BoundedVec = BoundedVec>; -impl TryFrom> for BoundedVec> { +impl TryFrom> + for BoundedVec> +{ + type Error = BoundedVecOutOfBounds; + + fn try_from(value: Vec) -> Result { + BoundedVec::>::from_vec(value, non_empty::()) + } +} + +impl TryFrom> for BoundedVec> { type Error = BoundedVecOutOfBounds; fn try_from(value: Vec) -> Result { - BoundedVec::>::from_vec(value, non_empty::()) + BoundedVec::>::from_vec(value, empty::()) } } // when feature(const_evaluatable_checked) is stable cover all array sizes (L..=U) -impl From<[T; L]> for BoundedVec> { +impl From<[T; L]> + for BoundedVec> +{ fn from(arr: [T; L]) -> Self { - BoundedVec { inner: arr.into(), _marker: <_>::default() } + BoundedVec { + inner: arr.into(), + _marker: core::marker::PhantomData, + } } } -impl From>> for Vec { +impl From>> + for Vec +{ fn from(v: BoundedVec>) -> Self { v.inner } @@ -530,7 +572,9 @@ pub trait OptBoundedVecToVec { fn to_vec(self) -> Vec; } -impl OptBoundedVecToVec for Option>> { +impl OptBoundedVecToVec + for Option>> +{ fn to_vec(self) -> Vec { self.map(|bv| bv.into()).unwrap_or_default() } @@ -546,7 +590,8 @@ mod arbitrary { use proptest::prelude::*; use proptest::strategy::BoxedStrategy; - impl Arbitrary for BoundedVec + impl Arbitrary + for BoundedVec> where T::Strategy: 'static, { @@ -555,7 +600,13 @@ mod arbitrary { fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { vec(any::(), L..=U) - .prop_map(|items| BoundedVec::from_vec(items, non_empty::()).unwrap()) + .prop_map(|items| { + BoundedVec::>::from_vec( + items, + non_empty::(), + ) + .unwrap() + }) .boxed() } } @@ -597,7 +648,10 @@ mod serde_impl { U ))); }; - Ok(BoundedVec { inner }) + Ok(BoundedVec { + inner, + _marker: core::marker::PhantomData, + }) } } @@ -649,7 +703,7 @@ mod tests { #[test] fn is_empty() { - let data: BoundedVec<_, 0, 8, EmptyWitness::<8>> = vec![1u8, 2].try_into().unwrap(); + let data: BoundedVec<_, 0, 8, EmptyWitness<8>> = vec![1u8, 2].try_into().unwrap(); assert!(!data.is_empty()); } From fe47982184b05bee785193f5dff52da10cf77f56 Mon Sep 17 00:00:00 2001 From: dzmitry-lahoda Date: Fri, 9 May 2025 11:55:08 +0100 Subject: [PATCH 07/15] private ctor --- src/bounded_vec.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/bounded_vec.rs b/src/bounded_vec.rs index eaf7c84..ef7fa2b 100644 --- a/src/bounded_vec.rs +++ b/src/bounded_vec.rs @@ -36,11 +36,11 @@ pub enum BoundedVecOutOfBounds { /// Compile-time proof of valid bounds. Must be consturcted with same bounds to instantiate `BoundedVec`. #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct NonEmptyWitness; +pub struct NonEmptyWitness(()); /// Possibly empty vector with upper bound. #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct EmptyWitness; +pub struct EmptyWitness(()); /// Type a compile-time proof of valid bounds pub const fn non_empty() -> NonEmptyWitness { @@ -51,12 +51,12 @@ pub const fn non_empty() -> NonEmptyWitness + NonEmptyWitness::(()) } /// Type a compile-time proof for possibly empty vector with upper bound pub const fn empty() -> EmptyWitness { - EmptyWitness:: + EmptyWitness::(()) } impl BoundedVec> { From 847ae8008a2e542a1ccb9886ca502b8d99c486e2 Mon Sep 17 00:00:00 2001 From: dzmitry-lahoda Date: Fri, 9 May 2025 11:58:56 +0100 Subject: [PATCH 08/15] some sugar --- src/bounded_vec.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/bounded_vec.rs b/src/bounded_vec.rs index ef7fa2b..1435628 100644 --- a/src/bounded_vec.rs +++ b/src/bounded_vec.rs @@ -477,6 +477,13 @@ impl BoundedVec = BoundedVec>; +/// Possibly empty Vec with upper-bound on its length +pub type EmptyBoundedVec = BoundedVec>; + +/// Non-empty Vec with bounded length +pub type NonEmptyBoundedVec = + BoundedVec>; + impl TryFrom> for BoundedVec> { From ecb865f6d66bb9b7247c221c9d9b4391a0c63f5c Mon Sep 17 00:00:00 2001 From: dzmitry-lahoda Date: Fri, 9 May 2025 13:52:10 +0100 Subject: [PATCH 09/15] oh can remove ctro noise --- src/bounded_vec.rs | 40 +++++++++++----------------------------- 1 file changed, 11 insertions(+), 29 deletions(-) diff --git a/src/bounded_vec.rs b/src/bounded_vec.rs index 1435628..cd805e1 100644 --- a/src/bounded_vec.rs +++ b/src/bounded_vec.rs @@ -79,10 +79,8 @@ impl BoundedVec> { /// let data: BoundedVec<_, 0, 8, EmptyWitness<8>> = /// BoundedVec::<_, 0, 8, EmptyWitness<8>>::from_vec(vec![1u8, 2], empty::<8>()).unwrap(); /// ``` - pub fn from_vec( - items: Vec, - _witness: EmptyWitness, - ) -> Result { + pub fn from_vec(items: Vec) -> Result { + let _witness = EmptyWitness::(()); let len = items.len(); if len > U { Err(BoundedVecOutOfBounds::UpperBoundError { @@ -236,10 +234,8 @@ impl BoundedVec> = /// BoundedVec::<_, 2, 8, NonEmptyWitness<2, 8>>::from_vec(vec![1u8, 2], non_empty::<2, 8>()).unwrap(); /// ``` - pub fn from_vec( - items: Vec, - _witness: NonEmptyWitness, - ) -> Result { + pub fn from_vec(items: Vec) -> Result { + let _witness = NonEmptyWitness::(()); let len = items.len(); if len < L { Err(BoundedVecOutOfBounds::LowerBoundError { @@ -387,10 +383,7 @@ impl BoundedVec>::from_vec(out, non_empty::()) - .unwrap(), - ) + Ok(BoundedVec::>::from_vec(out).unwrap()) } /// Create a new `BoundedVec` by mapping references of `self` elements @@ -425,10 +418,7 @@ impl BoundedVec>::from_vec(out, non_empty::()) - .unwrap(), - ) + Ok(BoundedVec::>::from_vec(out).unwrap()) } /// Returns the last and all the rest of the elements @@ -467,9 +457,7 @@ impl BoundedVec>::from_vec(v, non_empty::())?, - )) + Ok(Some(Self::from_vec(v)?)) } } } @@ -490,7 +478,7 @@ impl TryFrom> type Error = BoundedVecOutOfBounds; fn try_from(value: Vec) -> Result { - BoundedVec::>::from_vec(value, non_empty::()) + Self::from_vec(value) } } @@ -498,7 +486,7 @@ impl TryFrom> for BoundedVec> type Error = BoundedVecOutOfBounds; fn try_from(value: Vec) -> Result { - BoundedVec::>::from_vec(value, empty::()) + Self::from_vec(value) } } @@ -607,13 +595,7 @@ mod arbitrary { fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { vec(any::(), L..=U) - .prop_map(|items| { - BoundedVec::>::from_vec( - items, - non_empty::(), - ) - .unwrap() - }) + .prop_map(|items| Self::from_vec(items, non_empty::()).unwrap()) .boxed() } } @@ -710,7 +692,7 @@ mod tests { #[test] fn is_empty() { - let data: BoundedVec<_, 0, 8, EmptyWitness<8>> = vec![1u8, 2].try_into().unwrap(); + let data: EmptyBoundedVec<_, 8> = vec![1u8, 2].try_into().unwrap(); assert!(!data.is_empty()); } From 2856bc4586c09de8a8e1e4f8ccff8c4566d8bbed Mon Sep 17 00:00:00 2001 From: dzmitry-lahoda Date: Fri, 9 May 2025 13:53:43 +0100 Subject: [PATCH 10/15] fixed tests again --- src/bounded_vec.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/bounded_vec.rs b/src/bounded_vec.rs index cd805e1..b2ce34f 100644 --- a/src/bounded_vec.rs +++ b/src/bounded_vec.rs @@ -77,7 +77,7 @@ impl BoundedVec> { /// use bounded_vec::empty; /// use bounded_vec::EmptyWitness; /// let data: BoundedVec<_, 0, 8, EmptyWitness<8>> = - /// BoundedVec::<_, 0, 8, EmptyWitness<8>>::from_vec(vec![1u8, 2], empty::<8>()).unwrap(); + /// BoundedVec::<_, 0, 8, EmptyWitness<8>>::from_vec(vec![1u8, 2]).unwrap(); /// ``` pub fn from_vec(items: Vec) -> Result { let _witness = EmptyWitness::(()); @@ -232,7 +232,7 @@ impl BoundedVec> = - /// BoundedVec::<_, 2, 8, NonEmptyWitness<2, 8>>::from_vec(vec![1u8, 2], non_empty::<2, 8>()).unwrap(); + /// BoundedVec::<_, 2, 8, NonEmptyWitness<2, 8>>::from_vec(vec![1u8, 2]).unwrap(); /// ``` pub fn from_vec(items: Vec) -> Result { let _witness = NonEmptyWitness::(()); @@ -595,7 +595,7 @@ mod arbitrary { fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { vec(any::(), L..=U) - .prop_map(|items| Self::from_vec(items, non_empty::()).unwrap()) + .prop_map(|items| Self::from_vec(items).unwrap()) .boxed() } } @@ -684,10 +684,10 @@ mod tests { #[test] fn from_vec() { - assert!(BoundedVec::::from_vec(vec![1, 2], non_empty::<2, 8>()).is_ok()); - assert!(BoundedVec::::from_vec(vec![], non_empty::<2, 8>()).is_err()); - assert!(BoundedVec::::from_vec(vec![1, 2], non_empty::<3, 8>()).is_err()); - assert!(BoundedVec::::from_vec(vec![1, 2, 3], non_empty::<1, 2>()).is_err()); + assert!(BoundedVec::::from_vec(vec![1, 2]).is_ok()); + assert!(BoundedVec::::from_vec(vec![]).is_err()); + assert!(BoundedVec::::from_vec(vec![1, 2]).is_err()); + assert!(BoundedVec::::from_vec(vec![1, 2, 3]).is_err()); } #[test] From eaaf26a51592201f772090b67b8e559f7e65e9a9 Mon Sep 17 00:00:00 2001 From: dzmitry-lahoda Date: Sat, 10 May 2025 10:45:06 +0100 Subject: [PATCH 11/15] ensured private ctor --- src/bounded_vec.rs | 103 +++++++++++++++++++++++---------------------- 1 file changed, 53 insertions(+), 50 deletions(-) diff --git a/src/bounded_vec.rs b/src/bounded_vec.rs index b2ce34f..4f19083 100644 --- a/src/bounded_vec.rs +++ b/src/bounded_vec.rs @@ -6,7 +6,7 @@ use thiserror::Error; /// Non-empty Vec bounded with minimal (L - lower bound) and maximal (U - upper bound) items quantity #[derive(PartialEq, Eq, Debug, Clone, Hash, PartialOrd, Ord)] -pub struct BoundedVec> { +pub struct BoundedVec> { inner: Vec, _marker: core::marker::PhantomData, } @@ -32,34 +32,37 @@ pub enum BoundedVecOutOfBounds { }, } -// NOTE: we can have proves if needed for some cases like 8/16/32/64 upper bound, so can make memory and serde more compile safe and efficient +mod witnesses { -/// Compile-time proof of valid bounds. Must be consturcted with same bounds to instantiate `BoundedVec`. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct NonEmptyWitness(()); + // NOTE: we can have proves if needed for some cases like 8/16/32/64 upper bound, so can make memory and serde more compile safe and efficient -/// Possibly empty vector with upper bound. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct EmptyWitness(()); + /// Compile-time proof of valid bounds. Must be consturcted with same bounds to instantiate `BoundedVec`. + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + pub struct NonEmpty(()); -/// Type a compile-time proof of valid bounds -pub const fn non_empty() -> NonEmptyWitness { - if L == 0 { - panic!("L must be greater than 0") - } - if L > U { - panic!("L must be less than or equal to U") - } + /// Possibly empty vector with upper bound. + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + pub struct Empty(()); - NonEmptyWitness::(()) -} + /// Type a compile-time proof of valid bounds + pub const fn non_empty() -> NonEmpty { + if L == 0 { + panic!("L must be greater than 0") + } + if L > U { + panic!("L must be less than or equal to U") + } + + NonEmpty::(()) + } -/// Type a compile-time proof for possibly empty vector with upper bound -pub const fn empty() -> EmptyWitness { - EmptyWitness::(()) + /// Type a compile-time proof for possibly empty vector with upper bound + pub const fn empty() -> Empty { + Empty::(()) + } } -impl BoundedVec> { +impl BoundedVec> { /// Creates new BoundedVec or returns error if items count is out of bounds /// /// # Parameters @@ -76,11 +79,11 @@ impl BoundedVec> { /// use bounded_vec::BoundedVec; /// use bounded_vec::empty; /// use bounded_vec::EmptyWitness; - /// let data: BoundedVec<_, 0, 8, EmptyWitness<8>> = - /// BoundedVec::<_, 0, 8, EmptyWitness<8>>::from_vec(vec![1u8, 2]).unwrap(); + /// let data: BoundedVec<_, 0, 8, witnesses::Empty<8>> = + /// BoundedVec::<_, 0, 8, witnesses::Empty<8>>::from_vec(vec![1u8, 2]).unwrap(); /// ``` pub fn from_vec(items: Vec) -> Result { - let _witness = EmptyWitness::(()); + let _witness = witnesses::empty::(); let len = items.len(); if len > U { Err(BoundedVecOutOfBounds::UpperBoundError { @@ -104,7 +107,7 @@ impl BoundedVec> { /// use bounded_vec::EmptyWitness; /// use std::convert::TryInto; /// - /// let data: BoundedVec> = vec![1u8, 2].try_into().unwrap(); + /// let data: BoundedVec> = vec![1u8, 2].try_into().unwrap(); /// assert_eq!(data.first(), Some(&1u8)); /// ``` pub fn first(&self) -> Option<&T> { @@ -120,7 +123,7 @@ impl BoundedVec> { /// use bounded_vec::EmptyWitness; /// use std::convert::TryInto; /// - /// let data: BoundedVec> = vec![1u8, 2].try_into().unwrap(); + /// let data: BoundedVec> = vec![1u8, 2].try_into().unwrap(); /// assert_eq!(data.is_empty(), false); /// ``` pub fn is_empty(&self) -> bool { @@ -136,7 +139,7 @@ impl BoundedVec> { /// use bounded_vec::EmptyWitness; /// use std::convert::TryInto; /// - /// let data: BoundedVec> = vec![1u8, 2].try_into().unwrap(); + /// let data: BoundedVec> = vec![1u8, 2].try_into().unwrap(); /// assert_eq!(data.last(), Some(&2u8)); /// ``` pub fn last(&self) -> Option<&T> { @@ -213,7 +216,7 @@ impl BoundedVec { } } -impl BoundedVec> { +impl BoundedVec> { /// Creates new BoundedVec or returns error if items count is out of bounds /// /// # Parameters @@ -231,11 +234,11 @@ impl BoundedVec> = - /// BoundedVec::<_, 2, 8, NonEmptyWitness<2, 8>>::from_vec(vec![1u8, 2]).unwrap(); + /// let data: BoundedVec<_, 2, 8, witnesses::NonEmpty<2, 8>> = + /// BoundedVec::<_, 2, 8, witnesses::NonEmpty<2, 8>>::from_vec(vec![1u8, 2]).unwrap(); /// ``` pub fn from_vec(items: Vec) -> Result { - let _witness = NonEmptyWitness::(()); + let _witness = witnesses::non_empty::(); let len = items.len(); if len < L { Err(BoundedVecOutOfBounds::LowerBoundError { @@ -312,7 +315,7 @@ impl BoundedVec(self, map_fn: F) -> BoundedVec> + pub fn mapped(self, map_fn: F) -> BoundedVec> where F: FnMut(T) -> N, { @@ -335,7 +338,7 @@ impl BoundedVec(&self, map_fn: F) -> BoundedVec> + pub fn mapped_ref(&self, map_fn: F) -> BoundedVec> where F: FnMut(&T) -> N, { @@ -373,7 +376,7 @@ impl BoundedVec( self, map_fn: F, - ) -> Result>, E> + ) -> Result>, E> where F: FnMut(T) -> Result, { @@ -383,7 +386,7 @@ impl BoundedVec>::from_vec(out).unwrap()) + Ok(BoundedVec::>::from_vec(out).unwrap()) } /// Create a new `BoundedVec` by mapping references of `self` elements @@ -408,7 +411,7 @@ impl BoundedVec( &self, map_fn: F, - ) -> Result>, E> + ) -> Result>, E> where F: FnMut(&T) -> Result, { @@ -418,7 +421,7 @@ impl BoundedVec>::from_vec(out).unwrap()) + Ok(BoundedVec::>::from_vec(out).unwrap()) } /// Returns the last and all the rest of the elements @@ -428,7 +431,7 @@ impl BoundedVec BoundedVec<(usize, T), L, U, NonEmptyWitness> { + pub fn enumerated(self) -> BoundedVec<(usize, T), L, U, witnesses::NonEmpty> { #[allow(clippy::unwrap_used)] self.inner .into_iter() @@ -453,7 +456,7 @@ impl BoundedVec, - ) -> Result>>, BoundedVecOutOfBounds> { + ) -> Result>>, BoundedVecOutOfBounds> { if v.is_empty() { Ok(None) } else { @@ -463,17 +466,17 @@ impl BoundedVec = BoundedVec>; +pub type NonEmptyVec = BoundedVec>; /// Possibly empty Vec with upper-bound on its length -pub type EmptyBoundedVec = BoundedVec>; +pub type EmptyBoundedVec = BoundedVec>; /// Non-empty Vec with bounded length pub type NonEmptyBoundedVec = - BoundedVec>; + BoundedVec>; impl TryFrom> - for BoundedVec> + for BoundedVec> { type Error = BoundedVecOutOfBounds; @@ -482,7 +485,7 @@ impl TryFrom> } } -impl TryFrom> for BoundedVec> { +impl TryFrom> for BoundedVec> { type Error = BoundedVecOutOfBounds; fn try_from(value: Vec) -> Result { @@ -492,7 +495,7 @@ impl TryFrom> for BoundedVec> // when feature(const_evaluatable_checked) is stable cover all array sizes (L..=U) impl From<[T; L]> - for BoundedVec> + for BoundedVec> { fn from(arr: [T; L]) -> Self { BoundedVec { @@ -502,10 +505,10 @@ impl From<[T; L]> } } -impl From>> +impl From>> for Vec { - fn from(v: BoundedVec>) -> Self { + fn from(v: BoundedVec>) -> Self { v.inner } } @@ -568,7 +571,7 @@ pub trait OptBoundedVecToVec { } impl OptBoundedVecToVec - for Option>> + for Option>> { fn to_vec(self) -> Vec { self.map(|bv| bv.into()).unwrap_or_default() @@ -586,7 +589,7 @@ mod arbitrary { use proptest::strategy::BoxedStrategy; impl Arbitrary - for BoundedVec> + for BoundedVec> where T::Strategy: 'static, { From 9f4cfcaeee66500bf2fda4c03e470c0e113d2642 Mon Sep 17 00:00:00 2001 From: dzmitry-lahoda Date: Sat, 10 May 2025 10:50:50 +0100 Subject: [PATCH 12/15] fixed linters --- src/bounded_vec.rs | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/bounded_vec.rs b/src/bounded_vec.rs index 4f19083..893a140 100644 --- a/src/bounded_vec.rs +++ b/src/bounded_vec.rs @@ -4,7 +4,11 @@ use core::convert::{TryFrom, TryInto}; use core::slice::{Iter, IterMut}; use thiserror::Error; -/// Non-empty Vec bounded with minimal (L - lower bound) and maximal (U - upper bound) items quantity +/// Non-empty Vec bounded with minimal (L - lower bound) and maximal (U - upper bound) items quantity. +/// +/// # Type Parameters +/// +/// * `W` - witness type to prove vector ranges and shape if interface accordingly #[derive(PartialEq, Eq, Debug, Clone, Hash, PartialOrd, Ord)] pub struct BoundedVec> { inner: Vec, @@ -32,7 +36,8 @@ pub enum BoundedVecOutOfBounds { }, } -mod witnesses { +/// Module for type witnesses used to prove vector bounds at compile time +pub mod witnesses { // NOTE: we can have proves if needed for some cases like 8/16/32/64 upper bound, so can make memory and serde more compile safe and efficient @@ -68,7 +73,6 @@ impl BoundedVec> { /// # Parameters /// /// * `items` - vector of items within bounds - /// * `_witness` - compile-time proof of valid bounds create via `empty::()` function call /// /// # Errors /// @@ -77,8 +81,7 @@ impl BoundedVec> { /// # Example /// ``` /// use bounded_vec::BoundedVec; - /// use bounded_vec::empty; - /// use bounded_vec::EmptyWitness; + /// use bounded_vec::witnesses; /// let data: BoundedVec<_, 0, 8, witnesses::Empty<8>> = /// BoundedVec::<_, 0, 8, witnesses::Empty<8>>::from_vec(vec![1u8, 2]).unwrap(); /// ``` @@ -103,8 +106,7 @@ impl BoundedVec> { /// # Example /// ``` /// use bounded_vec::BoundedVec; - /// use bounded_vec::empty; - /// use bounded_vec::EmptyWitness; + /// use bounded_vec::witnesses; /// use std::convert::TryInto; /// /// let data: BoundedVec> = vec![1u8, 2].try_into().unwrap(); @@ -119,8 +121,7 @@ impl BoundedVec> { /// # Example /// ``` /// use bounded_vec::BoundedVec; - /// use bounded_vec::empty; - /// use bounded_vec::EmptyWitness; + /// use bounded_vec::witnesses; /// use std::convert::TryInto; /// /// let data: BoundedVec> = vec![1u8, 2].try_into().unwrap(); @@ -135,8 +136,7 @@ impl BoundedVec> { /// # Example /// ``` /// use bounded_vec::BoundedVec; - /// use bounded_vec::empty; - /// use bounded_vec::EmptyWitness; + /// use bounded_vec::witnesses; /// use std::convert::TryInto; /// /// let data: BoundedVec> = vec![1u8, 2].try_into().unwrap(); @@ -222,7 +222,6 @@ impl BoundedVec()` function call /// /// # Errors /// @@ -232,8 +231,7 @@ impl BoundedVec> = /// BoundedVec::<_, 2, 8, witnesses::NonEmpty<2, 8>>::from_vec(vec![1u8, 2]).unwrap(); /// ``` From d9bd97e8918bcae14fd6fa29edad8e5389f6bb0a Mon Sep 17 00:00:00 2001 From: dzmitry-lahoda Date: Sat, 10 May 2025 10:54:08 +0100 Subject: [PATCH 13/15] fmt --- src/bounded_vec.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bounded_vec.rs b/src/bounded_vec.rs index 893a140..836cf2a 100644 --- a/src/bounded_vec.rs +++ b/src/bounded_vec.rs @@ -5,9 +5,9 @@ use core::slice::{Iter, IterMut}; use thiserror::Error; /// Non-empty Vec bounded with minimal (L - lower bound) and maximal (U - upper bound) items quantity. -/// +/// /// # Type Parameters -/// +/// /// * `W` - witness type to prove vector ranges and shape if interface accordingly #[derive(PartialEq, Eq, Debug, Clone, Hash, PartialOrd, Ord)] pub struct BoundedVec> { From be8f08b116503fda0c3ea47e53dd9a067a7db1f8 Mon Sep 17 00:00:00 2001 From: dzmitry-lahoda Date: Sat, 10 May 2025 12:32:44 +0100 Subject: [PATCH 14/15] const around --- src/bounded_vec.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/bounded_vec.rs b/src/bounded_vec.rs index 836cf2a..2966a3e 100644 --- a/src/bounded_vec.rs +++ b/src/bounded_vec.rs @@ -86,7 +86,9 @@ impl BoundedVec> { /// BoundedVec::<_, 0, 8, witnesses::Empty<8>>::from_vec(vec![1u8, 2]).unwrap(); /// ``` pub fn from_vec(items: Vec) -> Result { - let _witness = witnesses::empty::(); + const { + let _witness = witnesses::empty::(); + } let len = items.len(); if len > U { Err(BoundedVecOutOfBounds::UpperBoundError { @@ -236,7 +238,9 @@ impl BoundedVec>::from_vec(vec![1u8, 2]).unwrap(); /// ``` pub fn from_vec(items: Vec) -> Result { - let _witness = witnesses::non_empty::(); + const { + let _witness = witnesses::non_empty::(); + } let len = items.len(); if len < L { Err(BoundedVecOutOfBounds::LowerBoundError { From ee04232c9de2e8f1536deca89807addc3739d9c1 Mon Sep 17 00:00:00 2001 From: dzmitry-lahoda Date: Sat, 10 May 2025 12:45:10 +0100 Subject: [PATCH 15/15] oh, just add const inside --- src/bounded_vec.rs | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/bounded_vec.rs b/src/bounded_vec.rs index 2966a3e..3461ff6 100644 --- a/src/bounded_vec.rs +++ b/src/bounded_vec.rs @@ -51,19 +51,21 @@ pub mod witnesses { /// Type a compile-time proof of valid bounds pub const fn non_empty() -> NonEmpty { - if L == 0 { - panic!("L must be greater than 0") - } - if L > U { - panic!("L must be less than or equal to U") - } + const { + if L == 0 { + panic!("L must be greater than 0") + } + if L > U { + panic!("L must be less than or equal to U") + } - NonEmpty::(()) + NonEmpty::(()) + } } /// Type a compile-time proof for possibly empty vector with upper bound pub const fn empty() -> Empty { - Empty::(()) + const { Empty::(()) } } } @@ -86,9 +88,7 @@ impl BoundedVec> { /// BoundedVec::<_, 0, 8, witnesses::Empty<8>>::from_vec(vec![1u8, 2]).unwrap(); /// ``` pub fn from_vec(items: Vec) -> Result { - const { - let _witness = witnesses::empty::(); - } + let _witness = witnesses::empty::(); let len = items.len(); if len > U { Err(BoundedVecOutOfBounds::UpperBoundError { @@ -238,9 +238,7 @@ impl BoundedVec>::from_vec(vec![1u8, 2]).unwrap(); /// ``` pub fn from_vec(items: Vec) -> Result { - const { - let _witness = witnesses::non_empty::(); - } + let _witness = witnesses::non_empty::(); let len = items.len(); if len < L { Err(BoundedVecOutOfBounds::LowerBoundError {