Skip to content

Commit

Permalink
Implement Iterator for StorageVec (#6821)
Browse files Browse the repository at this point in the history
## Description

This PR implements `Iterator` for `StorageVec`.

Iterating over a `StorageVec` via `for` loop should now be the preferred
option. Compared to the traversal via `while` loop and incrementing
index, the `for` loop is:
- more performant. `for` loop eliminates the redundant boundary checks
done in every `get()` call in the `while` loop.
- more idiomatic and convenient.

Closes #6796.

## Checklist

- [x] I have linked to any relevant issues.
- [x] I have commented my code, particularly in hard-to-understand
areas.
- [x] I have updated the documentation where relevant (API docs, the
reference, and the Sway book).
- [ ] If my change requires substantial documentation changes, I have
[requested support from the DevRel
team](https://github.com/FuelLabs/devrel-requests/issues/new/choose)
- [x] I have added tests that prove my fix is effective or that my
feature works.
- [ ] I have added (or requested a maintainer to add) the necessary
`Breaking*` or `New Feature` labels where relevant.
- [x] I have done my best to ensure that my PR adheres to [the Fuel Labs
Code Review
Standards](https://github.com/FuelLabs/rfcs/blob/master/text/code-standards/external-contributors.md).
- [x] I have requested a review from the relevant team or maintainers.

---------

Co-authored-by: bitzoic <[email protected]>
  • Loading branch information
ironcev and bitzoic authored Jan 14, 2025
1 parent e40ebf4 commit 4fe6870
Show file tree
Hide file tree
Showing 16 changed files with 1,010 additions and 143 deletions.
12 changes: 12 additions & 0 deletions docs/book/src/basics/control_flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,18 @@ for element in vector.iter() {

You need the `for` keyword, some pattern that contains variable names such as `element` in this case, the `ìn` keyword followed by an iterator, and a block of code inside the curly braces (`{...}`) to execute each iteration. `vector.iter()` in the example above returns an iterator for the `vector`. In each iteration, the value of `element` is updated with the next value in the iterator until the end of the vector is reached and the `for` loop iteration ends.

Modifying the `vector` during iteration, by e.g. adding or removing elements, is a logical error and results in an [undefined behavior](../reference/undefined_behavior.md):

```sway
// The behavior of this `for` loop is undefined because
// the `vector` gets modified within the loop.
for element in vector.iter() {
if element == 3 {
vector.push(6); // Modification of the vector!
}
}
```

### `break` and `continue`

`break` and `continue` keywords are available to use inside the body of a `while` or `for` loop. The purpose of the `break` statement is to break out of a loop early:
Expand Down
4 changes: 2 additions & 2 deletions docs/book/src/common-collections/storage_vec.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,13 @@ When the `get` method is passed an index that is outside the vector, it returns

## Iterating over the Values in a Vector

To access each element in a vector in turn, we would iterate through all of the valid indices using a `while` loop and the `len` method as shown below:
Iterating over a storage vector is conceptually the same as [iterating over a `Vec<T>`](./vec.md). The only difference is an additional call to `read()` to actually read the stored value.

```sway
{{#include ../../../../examples/storage_vec/src/main.sw:storage_vec_iterate}}
```

Again, this is quite similar to iterating over the elements of a `Vec<T>` where we use the method `len` to return the length of the vector. We also call the method `unwrap` to extract the `Option` returned by `get` followed by a call to `read()` to actually read the stored value. We know that `unwrap` will not fail (i.e. will not cause a revert) because each index `i` passed to `get` is known to be smaller than the length of the vector.
Note that **modifying a vector during iteration, by e.g. adding or removing elements, is a logical error and results in an [undefined behavior](../reference/undefined_behavior.md)**:

## Using an Enum to store Multiple Types

Expand Down
22 changes: 20 additions & 2 deletions docs/book/src/common-collections/vec.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,32 @@ When the `get` method is passed an index that is outside the vector, it returns

## Iterating over the Values in a Vector

To access each element in a vector in turn, we would iterate through all of the valid indices using a `while` loop and the `len` method as shown below:
To access elements of a vector, we can iterate through the valid indices using a `while` loop and the `len` method as shown below:

```sway
{{#include ../../../../examples/vec/src/main.sw:vec_iterate}}
{{#include ../../../../examples/vec/src/main.sw:vec_iterate_while}}
```

Note two details here. First, we use the method `len` which returns the length of the vector. Second, we call the method `unwrap` to extract the `Option` returned by `get`. We know that `unwrap` will not fail (i.e. will not cause a revert) because each index `i` passed to `get` is known to be smaller than the length of the vector.

The idiomatic and convenient way to access each element in a vector in turn, is to use the `for` loop in the combination with the `iter` method. The `iter` method returns an iterator that iterates over all the elements of the vector sequentially.

```sway
{{#include ../../../../examples/vec/src/main.sw:vec_iterate_for}}
```

Note that **modifying a vector during iteration, by e.g. adding or removing elements, is a logical error and results in an [undefined behavior](../reference/undefined_behavior.md)**:

```sway
{{#include ../../../../examples/vec/src/main.sw:vec_iterate_for_undefined}}
```

Accessing vector elements via `while` loop should be used only when more control over traversal is needed. E.g., in the below example we iterate the vector backwards, accessing only every second element.

```sway
{{#include ../../../../examples/vec/src/main.sw:vec_iterate_custom}}
```

## Using an Enum to store Multiple Types

Vectors can only store values that are the same type. This can be inconvenient; there are definitely use cases for needing to store a list of items of different types. Fortunately, the variants of an enum are defined under the same enum type, so when we need one type to represent elements of different types, we can define and use an enum!
Expand Down
9 changes: 5 additions & 4 deletions docs/book/src/reference/undefined_behavior.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ This is not an exhaustive list, it may grow or shrink, there is no formal model
of Sway's semantics so there may be more behavior considered undefined. We
reserve the right to make some of the listed behavior defined in the future.

* Invalid arithmetic operations (overflows, underflows, division by zero, etc)
* Misuse of compiler intrinsics
* Incorrect use of inline assembly
* Reading and writing `raw_ptr` and `raw_slice`
* Invalid arithmetic operations (overflows, underflows, division by zero, etc.).
* Misuse of compiler intrinsics.
* Incorrect use of inline assembly.
* Reading and writing `raw_ptr` and `raw_slice`.
* Slicing and indexing out of bounds by directly using compiler intrinsics.
* Modifying collections while iterating over them using `Iterator`s.
23 changes: 23 additions & 0 deletions examples/storage_vec/src/main.sw
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,34 @@ impl StorageVecContract for Contract {
// ANCHOR: storage_vec_iterate
#[storage(read)]
fn iterate_over_a_storage_vec() {
// Iterate over all the elements
// in turn using the `while` loop.
// **This approach is not recommended.**
// For iterating over all the elements
// in turn use the `for` loop instead.
let mut i = 0;
while i < storage.v.len() {
log(storage.v.get(i).unwrap().read());
i += 1;
}

// The preferred and most performant way
// to iterate over all the elements in turn is
// to use the `for` loop.
for elem in storage.v.iter() {
log(elem.read());
}

// Use the `while` loop only when more
// control over traversal is needed.
// E.g., in the below example we iterate
// the vector backwards, accessing only
// every second element.
let mut i = storage.v.len() - 1;
while 0 <= i {
log(storage.v.get(i).unwrap().read());
i -= 2;
}
}
// ANCHOR_END: storage_vec_iterate
// ANCHOR: storage_vec_multiple_types_fn
Expand Down
26 changes: 24 additions & 2 deletions examples/vec/src/main.sw
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,35 @@ fn main() {
let does_not_exist = v.get(100);
// ...decide here how to handle an out-of-bounds access
// ANCHOR_END: vec_get_oob
// ANCHOR: vec_iterate
// ANCHOR: vec_iterate_while
let mut i = 0;
while i < v.len() {
log(v.get(i).unwrap());
i += 1;
}
// ANCHOR_END: vec_iterate
// ANCHOR_END: vec_iterate_while
// ANCHOR: vec_iterate_for
for elem in v.iter() {
log(elem);
}
// ANCHOR_END: vec_iterate_for
// ANCHOR: vec_iterate_for_undefined
for elem in v.iter() {
log(elem);
if elem == 3 {
v.push(6); // Modification causes undefined behavior!
}
}
// ANCHOR_END: vec_iterate_for_undefined
// ANCHOR: vec_iterate_custom
// Start from the end
let mut i = v.len() - 1;
while 0 <= i {
log(v.get(i).unwrap());
// Access every second element
i -= 2;
}
// ANCHOR_END: vec_iterate_custom
// ANCHOR: vec_multiple_data_types
enum TableCell {
Int: u64,
Expand Down
2 changes: 1 addition & 1 deletion sway-lib-core/src/storage.sw
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ impl<T> StorageKey<T> {
///
/// # Returns
///
/// * [StorageKey] - The newly create `StorageKey`.
/// * [StorageKey] - The newly created `StorageKey`.
///
/// # Examples
///
Expand Down
48 changes: 33 additions & 15 deletions sway-lib-std/src/iterator.sw
Original file line number Diff line number Diff line change
Expand Up @@ -8,35 +8,53 @@ pub trait Iterator {
type Item;
/// Advances the iterator and returns the next value.
///
/// # Additional Information
///
/// Returns [`None`] when iteration is finished. Individual iterator
/// implementations may choose to resume iteration, and so calling `next()`
/// again may or may not eventually start returning [`Some(Item)`] again at some
/// point.
///
/// # Examples
/// # Undefined Behavior
///
/// Modifying underlying collection during iteration is a logical error and
/// results in undefined behavior. E.g.:
///
/// ```sway
/// let mut vec = Vec::new();
///
/// vec.push(1);
///
/// Basic usage:
/// let mut iter = vec.iter();
///
/// vec.clear(); // Collection modified.
///
/// let _ = iter.next(); // Undefined behavior.
/// ```
/// let mut a = Vec::new();
///
/// a.push(1);
/// a.push(2);
/// a.push(3);
/// # Examples
///
/// ```sway
/// let mut vec = Vec::new();
///
/// vec.push(1);
/// vec.push(2);
/// vec.push(3);
///
/// let mut iter = a.iter();
/// let mut iter = vec.iter();
///
/// // A call to next() returns the next value...
/// assert_eq!(Some(1), iter.next());
/// assert_eq!(Some(2), iter.next());
/// assert_eq!(Some(3), iter.next());
/// assert_eq(Some(1), iter.next());
/// assert_eq(Some(2), iter.next());
/// assert_eq(Some(3), iter.next());
///
/// // ... and then None once it's over.
/// assert_eq!(None, iter.next());
/// // ... and then `None` once it's over.
/// assert_eq(None, iter.next());
///
/// // More calls may or may not return `None`. Here, they always will.
/// assert_eq!(None, iter.next());
/// assert_eq!(None, iter.next());
/// // More calls may or may not return `None`.
/// // In the case of `Vec`, they always will.
/// assert_eq(None, iter.next());
/// assert_eq(None, iter.next());
/// ```
fn next(ref mut self) -> Option<Self::Item>;
}
2 changes: 1 addition & 1 deletion sway-lib-std/src/storage/storage_key.sw
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ impl<T> StorageKey<T> {
///
/// ```sway
/// fn foo() {
/// let r: StorageKey<u64> = StorageKey::new(b256::zero(), 2, b256::zero());s
/// let r: StorageKey<u64> = StorageKey::new(b256::zero(), 2, b256::zero());
/// // Reads the third word from storage slot with key 0x000...0
/// let x: u64 = r.read();
/// }
Expand Down
64 changes: 64 additions & 0 deletions sway-lib-std/src/storage/storage_vec.sw
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use ::option::Option::{self, *};
use ::storage::storage_api::*;
use ::storage::storage_key::*;
use ::vec::Vec;
use ::iterator::Iterator;

/// A persistent vector struct.
pub struct StorageVec<V> {}
Expand Down Expand Up @@ -939,6 +940,69 @@ impl<V> StorageKey<StorageVec<V>> {
}
}
}

/// Returns an [Iterator] to iterate over this `StorageVec`.
///
/// # Returns
///
/// * [StorageVecIter<V>] - The struct which can be iterated over.
///
/// # Examples
///
/// ```sway
/// storage {
/// vec: StorageVec<u64> = StorageVec {},
/// }
///
/// fn foo() {
/// storage.vec.push(5);
/// storage.vec.push(10);
/// storage.vec.push(15);
///
/// // Get the iterator
/// let iter = storage.vec.iter();
///
/// assert_eq(5, iter.next().unwrap().read());
/// assert_eq(10, iter.next().unwrap().read());
/// assert_eq(15, iter.next().unwrap().read());
///
/// for elem in storage.vec.iter() {
/// let elem_value = elem.read();
/// log(elem_value);
/// }
/// }
/// ```
#[storage(read)]
pub fn iter(self) -> StorageVecIter<V> {
StorageVecIter {
values: self,
len: read::<u64>(self.field_id(), 0).unwrap_or(0),
index: 0,
}
}
}

pub struct StorageVecIter<V> {
values: StorageKey<StorageVec<V>>,
len: u64,
index: u64,
}

impl<V> Iterator for StorageVecIter<V> {
type Item = StorageKey<V>;
fn next(ref mut self) -> Option<Self::Item> {
if self.index >= self.len {
return None
}

let key = sha256(self.values.field_id());
let offset = offset_calculator::<V>(self.index);
let result = Some(StorageKey::<V>::new(key, offset, sha256((self.index, key))));

self.index += 1;

result
}
}

// Add padding to type so it can correctly use the storage api
Expand Down
Loading

0 comments on commit 4fe6870

Please sign in to comment.