From 0559ac3aa07f8a61553e2edce605ac3dd3e1d792 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Mon, 9 Sep 2024 12:27:13 -0300 Subject: [PATCH 1/2] chore: document BoundedVec --- noir_stdlib/src/collections/bounded_vec.nr | 313 ++++++++++++++++++++- 1 file changed, 300 insertions(+), 13 deletions(-) diff --git a/noir_stdlib/src/collections/bounded_vec.nr b/noir_stdlib/src/collections/bounded_vec.nr index a4c0f642a82..f95794411b1 100644 --- a/noir_stdlib/src/collections/bounded_vec.nr +++ b/noir_stdlib/src/collections/bounded_vec.nr @@ -1,45 +1,188 @@ use crate::{cmp::Eq, convert::From}; +/// A `BoundedVec` is a growable storage similar to a `Vec` except that it +/// is bounded with a maximum possible length. Unlike `Vec`, `BoundedVec` is not implemented +/// via slices and thus is not subject to the same restrictions slices are (notably, nested +/// slices - and thus nested vectors as well - are disallowed). +/// +/// Since a BoundedVec is backed by a normal array under the hood, growing the BoundedVec by +/// pushing an additional element is also more efficient - the length only needs to be increased +/// by one. +/// +/// For these reasons `BoundedVec` should generally be preferred over `Vec` when there +/// is a reasonable maximum bound that can be placed on the vector. +/// +/// Example: +/// +/// ```noir +/// let mut vector: BoundedVec = BoundedVec::new(); +/// for i in 0..5 { +/// vector.push(i); +/// } +/// assert(vector.len() == 5); +/// assert(vector.max_len() == 10); +/// ``` struct BoundedVec { storage: [T; MaxLen], len: u32, } impl BoundedVec { + /// Creates a new, empty vector of length zero. + /// + /// Since this container is backed by an array internally, it still needs an initial value + /// to give each element. To resolve this, each element is zeroed internally. This value + /// is guaranteed to be inaccessible unless `get_unchecked` is used. + /// + /// Example: + /// + /// ```noir + /// let empty_vector: BoundedVec = BoundedVec::new(); + /// assert(empty_vector.len() == 0); + /// ``` + /// + /// Note that whenever calling `new` the maximum length of the vector should always be specified + /// via a type signature: + /// + /// ```noir + /// fn good() -> BoundedVec { + /// // Ok! MaxLen is specified with a type annotation + /// let v1: BoundedVec = BoundedVec::new(); + /// let v2 = BoundedVec::new(); + /// + /// // Ok! MaxLen is known from the type of foo's return value + /// v2 + /// } + /// + /// fn bad() { + /// let mut v3 = BoundedVec::new(); + /// + /// // Not Ok! We don't know if v3's MaxLen is at least 1, and the compiler often infers 0 by default. + /// v3.push(5); + /// } + /// ``` + /// + /// This defaulting of `MaxLen` (and numeric generics in general) to zero may change in future noir versions + /// but for now make sure to use type annotations when using bounded vectors. Otherwise, you will receive a + /// constraint failure at runtime when the vec is pushed to. pub fn new() -> Self { let zeroed = crate::mem::zeroed(); BoundedVec { storage: [zeroed; MaxLen], len: 0 } } - /// Get an element from the vector at the given index. - /// Panics if the given index points beyond the end of the vector (`self.len()`). + /// Retrieves an element from the vector at the given index, starting from zero. + /// + /// If the given index is equal to or greater than the length of the vector, this + /// will issue a constraint failure. + /// + /// Example: + /// + /// ```noir + /// fn foo(v: BoundedVec) { + /// let first = v.get(0); + /// let last = v.get(v.len() - 1); + /// assert(first != last); + /// } + /// ``` pub fn get(self, index: u32) -> T { assert(index < self.len, "Attempted to read past end of BoundedVec"); self.get_unchecked(index) } - /// Get an element from the vector at the given index. - /// Responds with undefined data for `index` where `self.len < index < self.max_len()`. + /// Retrieves an element from the vector at the given index, starting from zero, without + /// performing a bounds check. + /// + /// Since this function does not perform a bounds check on length before accessing the element, + /// it is unsafe! Use at your own risk! + /// + /// Example: + /// + /// ```noir + /// fn sum_of_first_three(v: BoundedVec) -> u32 { + /// // Always ensure the length is larger than the largest + /// // index passed to get_unchecked + /// assert(v.len() > 2); + /// let first = v.get_unchecked(0); + /// let second = v.get_unchecked(1); + /// let third = v.get_unchecked(2); + /// first + second + third + /// } + /// ``` pub fn get_unchecked(self, index: u32) -> T { self.storage[index] } - /// Write an element to the vector at the given index. - /// Panics if the given index points beyond the end of the vector (`self.len()`). + /// Writes an element to the vector at the given index, starting from zero. + /// + /// If the given index is equal to or greater than the length of the vector, this will issue a constraint failure. + /// + /// Example: + /// + /// ```noir + /// fn foo(v: BoundedVec) { + /// let first = v.get(0); + /// assert(first != 42); + /// v.set(0, 42); + /// let new_first = v.get(0); + /// assert(new_first == 42); + /// } + /// ``` pub fn set(&mut self, index: u32, value: T) { assert(index < self.len, "Attempted to write past end of BoundedVec"); self.set_unchecked(index, value) } - /// Write an element to the vector at the given index. - /// Does not check whether the passed `index` is a valid index within the vector. - /// - /// Silently writes past the end of the vector for `index` where `self.len < index < self.max_len()` - /// Panics if the given index points beyond the maximum length of the vector (`self.max_len()`). + /// Writes an element to the vector at the given index, starting from zero, without performing a bounds check. + /// + /// Since this function does not perform a bounds check on length before accessing the element, it is unsafe! Use at your own risk! + /// + /// Example: + /// + /// ```noir + /// fn set_unchecked_example() { + /// let mut vec: BoundedVec = BoundedVec::new(); + /// vec.extend_from_array([1, 2]); + /// + /// // Here we're safely writing within the valid range of `vec` + /// // `vec` now has the value [42, 2] + /// vec.set_unchecked(0, 42); + /// + /// // We can then safely read this value back out of `vec`. + /// // Notice that we use the checked version of `get` which would prevent reading unsafe values. + /// assert_eq(vec.get(0), 42); + /// + /// // We've now written past the end of `vec`. + /// // As this index is still within the maximum potential length of `v`, + /// // it won't cause a constraint failure. + /// vec.set_unchecked(2, 42); + /// println(vec); + /// + /// // This will write past the end of the maximum potential length of `vec`, + /// // it will then trigger a constraint failure. + /// vec.set_unchecked(5, 42); + /// println(vec); + /// } + /// ``` pub fn set_unchecked(&mut self, index: u32, value: T) { self.storage[index] = value; } + /// Pushes an element to the end of the vector. This increases the length + /// of the vector by one. + /// + /// Panics if the new length of the vector will be greater than the max length. + /// + /// Example: + /// + /// ```noir + /// let mut v: BoundedVec = BoundedVec::new(); + /// + /// v.push(1); + /// v.push(2); + /// + /// // Panics with failed assertion "push out of bounds" + /// v.push(3); + /// ``` pub fn push(&mut self, elem: T) { assert(self.len < MaxLen, "push out of bounds"); @@ -47,20 +190,82 @@ impl BoundedVec { self.len += 1; } + /// Returns the current length of this vector + /// + /// Example: + /// + /// ```noir + /// let mut v: BoundedVec = BoundedVec::new(); + /// assert(v.len() == 0); + /// + /// v.push(100); + /// assert(v.len() == 1); + /// + /// v.push(200); + /// v.push(300); + /// v.push(400); + /// assert(v.len() == 4); + /// + /// let _ = v.pop(); + /// let _ = v.pop(); + /// assert(v.len() == 2); + /// ``` pub fn len(self) -> u32 { self.len } + /// Returns the maximum length of this vector. This is always + /// equal to the `MaxLen` parameter this vector was initialized with. + /// + /// Example: + /// + /// ```noir + /// let mut v: BoundedVec = BoundedVec::new(); + /// + /// assert(v.max_len() == 5); + /// v.push(10); + /// assert(v.max_len() == 5); + /// ``` pub fn max_len(_self: BoundedVec) -> u32 { MaxLen } - // This is a intermediate method, while we don't have an - // .extend method + /// Returns the internal array within this vector. + /// + /// Since arrays in Noir are immutable, mutating the returned storage array will not mutate + /// the storage held internally by this vector. + /// + /// Note that uninitialized elements may be zeroed out! + /// + /// Example: + /// + /// ```noir + /// let mut v: BoundedVec = BoundedVec::new(); + /// + /// assert(v.storage() == [0, 0, 0, 0, 0]); + /// + /// v.push(57); + /// assert(v.storage() == [57, 0, 0, 0, 0]); + /// ``` pub fn storage(self) -> [T; MaxLen] { self.storage } + /// Pushes each element from the given array to this vector. + /// + /// Panics if pushing each element would cause the length of this vector + /// to exceed the maximum length. + /// + /// Example: + /// + /// ```noir + /// let mut vec: BoundedVec = BoundedVec::new(); + /// vec.extend_from_array([2, 4]); + /// + /// assert(vec.len == 2); + /// assert(vec.get(0) == 2); + /// assert(vec.get(1) == 4); + /// ``` pub fn extend_from_array(&mut self, array: [T; Len]) { let new_len = self.len + array.len(); assert(new_len <= MaxLen, "extend_from_array out of bounds"); @@ -70,6 +275,21 @@ impl BoundedVec { self.len = new_len; } + /// Pushes each element from the given slice to this vector. + /// + /// Panics if pushing each element would cause the length of this vector + /// to exceed the maximum length. + /// + /// Example: + /// + /// ```noir + /// let mut vec: BoundedVec = BoundedVec::new(); + /// vec.extend_from_slice(&[2, 4]); + /// + /// assert(vec.len == 2); + /// assert(vec.get(0) == 2); + /// assert(vec.get(1) == 4); + /// ``` pub fn extend_from_slice(&mut self, slice: [T]) { let new_len = self.len + slice.len(); assert(new_len <= MaxLen, "extend_from_slice out of bounds"); @@ -79,6 +299,22 @@ impl BoundedVec { self.len = new_len; } + /// Pushes each element from the other vector to this vector. The length of + /// the other vector is left unchanged. + /// + /// Panics if pushing each element would cause the length of this vector + /// to exceed the maximum length. + /// + /// ```noir + /// let mut v1: BoundedVec = BoundedVec::new(); + /// let mut v2: BoundedVec = BoundedVec::new(); + /// + /// v2.extend_from_array([1, 2, 3]); + /// v1.extend_from_bounded_vec(v2); + /// + /// assert(v1.storage() == [1, 2, 3, 0, 0]); + /// assert(v2.storage() == [1, 2, 3, 0, 0, 0, 0]); + /// ``` pub fn extend_from_bounded_vec(&mut self, vec: BoundedVec) { let append_len = vec.len(); let new_len = self.len + append_len; @@ -94,6 +330,14 @@ impl BoundedVec { self.len = new_len; } + /// Creates a new vector, populating it with values derived from an array input. + /// The maximum length of the vector is determined based on the type signature. + /// + /// Example: + /// + /// ```noir + /// let bounded_vec: BoundedVec = BoundedVec::from_array([1, 2, 3]) + /// ``` pub fn from_array(array: [T; Len]) -> Self { assert(Len <= MaxLen, "from array out of bounds"); let mut vec: BoundedVec = BoundedVec::new(); @@ -101,6 +345,26 @@ impl BoundedVec { vec } + /// Pops the element at the end of the vector. This will decrease the length + /// of the vector by one. + /// + /// Panics if the vector is empty. + /// + /// Example: + /// + /// ```noir + /// let mut v: BoundedVec = BoundedVec::new(); + /// v.push(1); + /// v.push(2); + /// + /// let two = v.pop(); + /// let one = v.pop(); + /// + /// assert(two == 2); + /// assert(one == 1); + /// // error: cannot pop from an empty vector + /// // let _ = v.pop(); + /// ``` pub fn pop(&mut self) -> T { assert(self.len > 0); self.len -= 1; @@ -110,6 +374,18 @@ impl BoundedVec { elem } + /// Returns true if the given predicate returns true for any element + /// in this vector. + /// + /// Example: + /// + /// ```noir + /// let mut v: BoundedVec = BoundedVec::new(); + /// v.extend_from_array([2, 4, 6]); + /// + /// let all_even = !v.any(|elem: u32| elem % 2 != 0); + /// assert(all_even); + /// ``` pub fn any(self, predicate: fn[Env](T) -> bool) -> bool { let mut ret = false; let mut exceeded_len = false; @@ -122,6 +398,17 @@ impl BoundedVec { ret } + /// Creates a new vector of equal size by calling a closure on each element in this vector. + /// + /// Example: + /// + /// ```noir + /// let vec: BoundedVec = BoundedVec::from_array([1, 2, 3, 4]); + /// let result = vec.map(|value| value * 2); + /// + /// let expected = BoundedVec::from_array([2, 4, 6, 8]); + /// assert_eq(result, expected); + /// ``` pub fn map(self, f: fn[Env](T) -> U) -> BoundedVec { let mut ret = BoundedVec::new(); ret.len = self.len(); From a26ce374eaaa59970d812ebd12c875d2bf1c23b5 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Mon, 9 Sep 2024 12:45:03 -0300 Subject: [PATCH 2/2] Fix code examples --- noir_stdlib/src/collections/bounded_vec.nr | 9 +++++---- .../noir_test_success/bounded_vec/src/main.nr | 12 ++++++------ 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/noir_stdlib/src/collections/bounded_vec.nr b/noir_stdlib/src/collections/bounded_vec.nr index f95794411b1..fede1b17c07 100644 --- a/noir_stdlib/src/collections/bounded_vec.nr +++ b/noir_stdlib/src/collections/bounded_vec.nr @@ -50,14 +50,14 @@ impl BoundedVec { /// let v1: BoundedVec = BoundedVec::new(); /// let v2 = BoundedVec::new(); /// - /// // Ok! MaxLen is known from the type of foo's return value + /// // Ok! MaxLen is known from the type of `good`'s return value /// v2 /// } /// /// fn bad() { + /// // Error: Type annotation needed + /// // The compiller can't infer `MaxLen` from the following code: /// let mut v3 = BoundedVec::new(); - /// - /// // Not Ok! We don't know if v3's MaxLen is at least 1, and the compiler often infers 0 by default. /// v3.push(5); /// } /// ``` @@ -362,8 +362,9 @@ impl BoundedVec { /// /// assert(two == 2); /// assert(one == 1); + /// /// // error: cannot pop from an empty vector - /// // let _ = v.pop(); + /// let _ = v.pop(); /// ``` pub fn pop(&mut self) -> T { assert(self.len > 0); diff --git a/test_programs/noir_test_success/bounded_vec/src/main.nr b/test_programs/noir_test_success/bounded_vec/src/main.nr index e5aa5f88a94..7b3e63df072 100644 --- a/test_programs/noir_test_success/bounded_vec/src/main.nr +++ b/test_programs/noir_test_success/bounded_vec/src/main.nr @@ -1,6 +1,6 @@ #[test] -fn test_vec_new_foo() { - foo(); +fn test_vec_new_good() { + good(); } #[test(should_fail)] @@ -9,19 +9,19 @@ fn test_vec_new_bad() { } // docs:start:new_example -fn foo() -> BoundedVec { +fn good() -> BoundedVec { // Ok! MaxLen is specified with a type annotation let v1: BoundedVec = BoundedVec::new(); let v2 = BoundedVec::new(); - // Ok! MaxLen is known from the type of foo's return value + // Ok! MaxLen is known from the type of `good`'s return value v2 } fn bad() { + // Error: Type annotation needed + // The compiller can't infer `MaxLen` from this code. let mut v3 = BoundedVec::new(); - - // Not Ok! We don't know if v3's MaxLen is at least 1, and the compiler often infers 0 by default. v3.push(5); } // docs:end:new_example