Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

<!-- next-header -->
## [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;
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "bounded-vec"
version = "0.7.1"
license = "CC0-1.0"
authors = ["Denys Zadorozhnyi <denys@zadorozhnyi.com>"]
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"

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

## bounded-vec
`BoundedVec<T, L, U>` - 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

Expand All @@ -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
Expand Down
64 changes: 39 additions & 25 deletions src/bounded_vec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,10 @@

/// 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<T, const L: usize, const U: usize>
// enable when feature(const_evaluatable_checked) is stable
// where
// Assert<{ L > 0 }>: IsTrue,
{
pub struct BoundedVec<T, const L: usize, const U: usize> {
inner: Vec<T>,
}

// enum Assert<const COND: bool> {}

// trait IsTrue {}

// impl IsTrue for Assert<true> {}

/// BoundedVec errors
#[derive(Error, PartialEq, Eq, Debug, Clone)]
pub enum BoundedVecOutOfBounds {
Expand All @@ -41,18 +31,42 @@
},
}

/// Compile-time proof of valid bounds. Must be consturcted with same bounds to instantiate `BoundedVec`.
#[derive(Clone, Copy)]
pub struct Proof<const A: usize, const B: usize>;

/// Type a compile-time proof of valid bounds
pub const fn prove<const L: usize, const U: usize>() -> Proof<L, U> {
if L == 0 {
panic!("L must be greater than 0")
}
if L > U {
panic!("L must be less than or equal to U")
}

Proof::<L, U>
}

impl<T, const L: usize, const U: usize> BoundedVec<T, L, U> {
/// 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::<L,U>()` 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<T>) -> Result<Self, BoundedVecOutOfBounds> {
// 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<T>, _: Proof<L, U>) -> Result<Self, BoundedVecOutOfBounds> {
let len = items.len();
if len < L {
Err(BoundedVecOutOfBounds::LowerBoundError {
Expand Down Expand Up @@ -248,7 +262,7 @@
out.push(map_fn(element)?);
}
#[allow(clippy::unwrap_used)]
Ok(BoundedVec::from_vec(out).unwrap())
Ok(BoundedVec::from_vec(out, prove::<L, U>()).unwrap())
}

/// Create a new `BoundedVec` by mapping references of `self` elements
Expand Down Expand Up @@ -280,7 +294,7 @@
out.push(map_fn(element)?);
}
#[allow(clippy::unwrap_used)]
Ok(BoundedVec::from_vec(out).unwrap())
Ok(BoundedVec::from_vec(out, prove::<L, U>()).unwrap())
}

/// Returns a reference for an element at index or `None` if out of bounds
Expand Down Expand Up @@ -341,7 +355,7 @@
if v.is_empty() {
Ok(None)
} else {
Ok(Some(BoundedVec::from_vec(v)?))
Ok(Some(BoundedVec::from_vec(v, prove::<L, U>())?))
}
}
}
Expand All @@ -353,7 +367,7 @@
type Error = BoundedVecOutOfBounds;

fn try_from(value: Vec<T>) -> Result<Self, Self::Error> {
BoundedVec::from_vec(value)
BoundedVec::from_vec(value, prove::<L, U>())
}
}

Expand Down Expand Up @@ -421,9 +435,9 @@
}
}

/// Option<BoundedVec<T, _, _>> to Vec<T>

Check warning on line 438 in src/bounded_vec.rs

View workflow job for this annotation

GitHub Actions / Intra-documentation links

unclosed HTML tag `T`
pub trait OptBoundedVecToVec<T> {
/// Option<BoundedVec<T, _, _>> to Vec<T>

Check warning on line 440 in src/bounded_vec.rs

View workflow job for this annotation

GitHub Actions / Intra-documentation links

unclosed HTML tag `T`
fn to_vec(self) -> Vec<T>;
}

Expand Down Expand Up @@ -452,7 +466,7 @@

fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
vec(any::<T>(), L..=U)
.prop_map(|items| BoundedVec::from_vec(items).unwrap())
.prop_map(|items| BoundedVec::from_vec(items, prove::<L, U>()).unwrap())
.boxed()
}
}
Expand Down Expand Up @@ -538,10 +552,10 @@

#[test]
fn from_vec() {
assert!(BoundedVec::<u8, 2, 8>::from_vec(vec![1, 2]).is_ok());
assert!(BoundedVec::<u8, 2, 8>::from_vec(vec![]).is_err());
assert!(BoundedVec::<u8, 3, 8>::from_vec(vec![1, 2]).is_err());
assert!(BoundedVec::<u8, 1, 2>::from_vec(vec![1, 2, 3]).is_err());
assert!(BoundedVec::<u8, 2, 8>::from_vec(vec![1, 2], prove::<2, 8>()).is_ok());
assert!(BoundedVec::<u8, 2, 8>::from_vec(vec![], prove::<2, 8>()).is_err());
assert!(BoundedVec::<u8, 3, 8>::from_vec(vec![1, 2], prove::<3, 8>()).is_err());
assert!(BoundedVec::<u8, 1, 2>::from_vec(vec![1, 2, 3], prove::<1, 2>()).is_err());
}

#[test]
Expand Down
Loading