7.2 存储向量 (Storage Vectors)

The second collection type we’ll look at is StorageVec<T>. Just like vectors on the heap (i.e. Vec<T>), storage vectors allow you to store more than one value in a single data structure where each value is assigned an index and can only store values of the same type. However, unlike Vec<T>, the elements of a StorageVec are stored in persistent storage, and consecutive elements are not necessarily stored in storage slots that have consecutive keys.

我们要看的第二种集聚类型是StorageVec<T>。就像堆上的向量(即Vec<T>)一样,存储向量允许您在单个数据结构中存储多个值,其中每个值都分配有一个索引并且只存储相同类型的值。但是,与Vec<T> 不同的是,StorageVec 的元素存储在 持久存储 中,并且连续的元素不一定存储在具有连续键的存储槽中。

In order to use StorageVec<T>, you must first import StorageVec as follows:

为了使用StorageVec<T>,您必须首先导入StorageVec,如下所示:

use std::storage::storage_vec::*;

Another major difference between Vec<T> and StorageVec<T> is that StorageVec<T> can only be used in a contract because only contracts are allowed to access persistent storage.

Vec<T>StorageVec<T> 之间的另一个主要区别是 StorageVec<T> 只能在合约中使用,因为只有合约才允许访问持久存储。

创建一个新的存储向量 (Creating a New Storage Vector)

To create a new empty storage vector, we have to declare the vector in a storage block as follows:

要创建一个新的空存储向量,我们必须在 storage 块中声明该向量,如下所示:

    v: StorageVec<u64> = StorageVec {},

Just like any other storage variable, two things are required when declaring a StorageVec: a type annotation and an initializer. The initializer is just an empty struct of type StorageVec because StorageVec<T> itself is an empty struct! Everything that is interesting about StorageVec<T> is implemented in its methods.

就像任何其他存储变量一样,声明 StorageVec 时需要两件事:类型注释和初始化程序。初始化器只是一个 StorageVec 类型的空结构,因为 StorageVec<T> 本身是一个空结构! StorageVec<T> 的所有有趣之处都在其方法中实现。

Storage vectors, just like Vec<T>, are implemented using generics which means that the StorageVec<T> type provided by the standard library can hold any type. When we create a storage vector to hold a specific type, we can specify the type within angle brackets. In the example above, we’ve told the Sway compiler that the StorageVec<T> in v will hold elements of the u64 type.

存储向量,就像 Vec<T> 一样,是使用泛型实现的,这意味着标准库提供的 StorageVec<T>类型可以容纳任何类型。当我们创建存储向量来保存特定类型时,我们可以在尖括号内指定类型。在上面的示例中,我们已经告诉 Sway 编译器,v 中的StorageVec<T> 将保存 u64 类型的元素。

更新存储向量 (Updating a Storage Vector)

To add elements to a storage vector, we can use the push method, as shown below:

要将元素添加到存储向量,我们可以使用push 方法,如下所示:

    #[storage(read, write)]
    fn push_to_storage_vec() {
        storage.v.push(5);
        storage.v.push(6);
        storage.v.push(7);
        storage.v.push(8);
    }

Note two details here. First, in order to use push, we need to first access the vector using the storage keyword. Second, because push requires accessing storage, a storage annotation is required on the ABI function that calls push. While it may seem that #[storage(write)] should be enough here, the read annotation is also required because each call to push requires reading (and then updating) the length of the storage vector which is also stored in persistent storage.

这里注意两个细节。首先,为了使用 push,我们需要先使用 storage 关键字访问向量。其次,因为 push 需要访问存储,所以调用 push 的 ABI 函数需要一个 storage 注释。虽然这里看起来 #[storage(write)] 应该足够了,但还需要 read 注释,因为每次调用 push 都需要 读取 (然后更新)存储向量的长度,这也是存储在持久存储中。

Note The storage annotation is also required for any private function defined in the contract that tries to push into the vector. 注意 合约中定义的,试图推入向量的任何私有函数也需要存储注释。

Note There is no need to add the mut keyword when declaring a StorageVec<T>. All storage variables are mutable by default. 注意 声明 StorageVec<T> 时无需添加 mut 关键字。默认情况下,所有存储变量都是可变的。

读取存储向量的元素 (Reading Elements of Storage Vectors)

To read a value stored in a vector at a particular index, you can use the get method as shown below:

要读取存储在特定索引处的向量中的值,您可以使用 get 方法,如下所示:

    #[storage(read)]
    fn read_from_storage_vec() {
        let third = storage.v.get(2);
        match third {
            Option::Some(third) => log(third.read()),
            Option::None => revert(42),
        }
    }

Note three details here. First, we use the index value of 2 to get the third element because vectors are indexed by number, starting at zero. Second, we get the third element by using the get method with the index passed as an argument, which gives us an Option<StorageKey<T>>. Third, the ABI function calling get only requires the annotation #[storage(read)] as one might expect because get does not write to storage.

这里注意三个细节。首先,我们使用 2 的索引值来获取第三个元素,因为向量是按数字索引的,从0开始。其次,我们通过使用 get 方法将索引作为参数传递来获取第三个元素,这为我们提供了 Option<StorageKey<T>>。第三,调用 get 的 ABI 函数只需要注释 #[storage(read)] 正如人们可能期望的那样,因为 get 不写入存储。

When the get method is passed an index that is outside the vector, it returns None without panicking. This is particularly useful if accessing an element beyond the range of the vector may happen occasionally under normal circumstances. Your code will then have logic to handle having either Some(element) or None. For example, the index could be coming as a contract method argument. If the argument passed is too large, the method get will return a None value, and the contract method may then decide to revert when that happens or return a meaningful error that tells the user how many items are in the current vector and give them another chance to pass a valid value.

当向 get 方法传递一个向量外部的索引时,它会返回 None 且不会出现恐慌。如果在正常情况下偶尔会访问超出向量范围的元素,这将特别有用。然后,您的代码将具有处理Some(element)None的逻辑。例如,索引可以作为合约方法参数出现。如果传递的参数太大,方法 get 将返回一个 None 值,然后合约方法可能决定在这种情况发生时恢复或返回一个有意义的错误,告诉用户当前向量中有多少项并给他们另一次传递有效值的机会。

迭代向量中的值 (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:

要依次访问向量中的每个元素,我们将使用 while 循环和 len 方法遍历所有有效索引,如下所示:

    #[storage(read)]
    fn iterate_over_a_storage_vec() {
        let mut i = 0;
        while i < storage.v.len() {
            log(storage.v.get(i).unwrap().read());
            i += 1;
        }
    }

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.

同样,这与迭代 Vec<T> 的元素非常相似,我们在其中使用len 方法返回向量的长度。我们还调用方法 unwrap 来提取 get 返回的 Option ,然后调用 read() 来实际读取存储的值。我们知道 unwrap 不会失败(即不会导致还原),因为已知传递给 get 的每个索引 i 都小于向量的长度。

使用穷举来存储多种类型 (Using an Enum to store Multiple Types)

Storage vectors, just like Vec<T>, can only store values that are the same type. Similarly to what we did for Vec<T> in the section Using an Enum to store Multiple Types, we can define an enum whose variants will hold the different value types, and all the enum variants will be considered the same type: that of the enum. This is shown below:

存储向量,就像 Vec<T> 一样,只能存储相同类型的值。类似于我们在[使用穷举存储多种类型](https://fuellabs.github.io/sway/v0.38.0/book/common-collections/vec.html #using-an-enum-to-store-multiple-types),我们可以定义一个穷举,其变体将包含不同的值类型,并且所有穷举变体将被视为同一类型:穷举的类型。如下所示:

enum TableCell {
    Int: u64,
    B256: b256,
    Boolean: bool,
}

Then we can declare a storage vector in a storage block to hold that enum and so, ultimately, holds different types:

然后我们可以在 storage 块中声明一个存储向量来保存该穷举,因此最终保存不同的类型:

    row: StorageVec<TableCell> = StorageVec {},

We can now push different enum variants to the storage vector as follows:

我们现在可以将不同的穷举变体推送到存储向量,如下所示:

    #[storage(read, write)]
    fn push_to_multiple_types_storage_vec() {
        storage.row.push(TableCell::Int(3));
        storage.row.push(TableCell::B256(0x0101010101010101010101010101010101010101010101010101010101010101));
        storage.row.push(TableCell::Boolean(true));
    }

Now that we’ve discussed some of the most common ways to use storage vectors, be sure to review the API documentation for all the many useful methods defined on StorageVec<T> by the standard library. For now, these can be found in the source code for StorageVec<T>. For example, in addition to push, a pop method removes and returns the last element, a remove method removes and returns the element at some chosen index within the vector, an insert method inserts an element at some chosen index within the vector, etc.

现在我们已经讨论了使用存储向量的一些最常见方法,请务必查看 API 文档以了解标准库在 StorageVec<T> 上定义的所有许多有用方法。目前,这些可以在 StorageVec<T> 的源代码 中找到。例如,除了 push 之外,pop 方法删除并返回最后一个元素,remove 方法删除并返回向量中某个选定索引处的元素,insert 方法在某个位置插入一个元素向量中选择的索引等

嵌套存储向量 (Nested Storage Vecs)

It is possible to nest storage vectors as follows:

可以按如下方式嵌套存储向量:

    nested_vec: StorageVec<StorageVec<u64>> = StorageVec {},

The nested vector can then be accessed as follows:

然后可以按如下方式访问嵌套向量:

    #[storage(read, write)]
    fn access_nested_vec() {
        storage.nested_vec.push(StorageVec {});
        storage.nested_vec.push(StorageVec {});

        let mut inner_vec0 = storage.nested_vec.get(0).unwrap();
        let mut inner_vec1 = storage.nested_vec.get(1).unwrap();

        inner_vec0.push(0);
        inner_vec0.push(1);

        inner_vec1.push(2);
        inner_vec1.push(3);
        inner_vec1.push(4);

        assert(inner_vec0.len() == 2);
        assert(inner_vec0.get(0).unwrap().read() == 0);
        assert(inner_vec0.get(1).unwrap().read() == 1);
        assert(inner_vec0.get(2).is_none());

        assert(inner_vec1.len() == 3);
        assert(inner_vec1.get(0).unwrap().read() == 2);
        assert(inner_vec1.get(1).unwrap().read() == 3);
        assert(inner_vec1.get(2).unwrap().read() == 4);
        assert(inner_vec1.get(3).is_none());
    }

Last updated