我们将要介绍的第二种集合类型是 StorageVec<T>
。与堆上的向量(即 Vec<T>
)类似,存储向量允许您将多个值存储在单个数据结构中,其中每个值被分配一个索引,并且只能存储相同类型的值。然而,与 Vec<T>
不同,StorageVec
的元素存储在 持久存储 中,并且连续的元素不一定存储在具有连续键的存储槽中。
要使用 StorageVec<T>
,必须首先导入 StorageVec
,如下所示:
use std::storage::storage_vec::*;
Vec<T>
和 StorageVec<T>
之间的另一个主要区别是 StorageVec<T>
只能在合约中使用,因为只有合约允许访问持久存储。
StorageVec
要创建一个新的空 StorageVec
,我们必须在 storage
块中声明向量,如下所示:
v: StorageVec<u64> = StorageVec {},
与任何其他存储变量一样,声明 StorageVec
时需要两样东西:类型注释和初始化器。初始化器只是类型为 StorageVec
的空结构体,因为 StorageVec<T>
本身就是一个空结构体!StorageVec<T>
的所有有趣的内容都在其方法中实现。
存储向量,就像 Vec<T>
一样,是使用泛型实现的,这意味着标准库提供的 StorageVec<T>
类型可以容纳任何类型。当我们创建一个用于保存特定类型的 StorageVec
时,我们可以在尖括号内指定类型。在上面的示例中,我们告诉 Sway 编译器 v
中的 StorageVec<T>
将保存 u64
类型的元素。
StorageVec
要向 StorageVec
添加元素,我们可以使用 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);
}
请注意两个细节。首先,为了使用 push
,我们需要首先使用 storage
关键字访问向量。其次,因为 push
需要访问存储,所以调用 push
的 ABI 函数需要添加 storage
注解。虽然 #[storage(write)]
看起来应该足够了,但是还需要 read
注解,因为每次调用 push
都需要 读取(然后更新)StorageVec
的长度,该长度也存储在持久存储中。
注意 对于尝试向向量中添加元素的任何私有函数,都需要在合约中添加存储注解。
注意 声明
StorageVec<T>
时无需添加mut
关键字。所有存储变量默认都是可变的。
要读取向量中特定索引处存储的值,您可以使用 get
方法,如下所示:
#[storage(read)]
fn read_from_storage_vec() {
let third = storage.v.get(2);
match third {
Some(third) => log(third.read()),
None => revert(42),
}
}
请注意三个细节。首先,我们使用索引值 2
来获取第三个元素,因为向量是通过数字索引的,从零开始。其次,我们使用带有索引的 get
方法来获取第三个元素,该索引作为参数传递给 get
,这会给我们一个 Option<StorageKey<T>>
。第三,调用 get
的 ABI 函数只需要注解 #[storage(read)]
,因为 get
不会写入存储。
当 get
方法传递一个超出向量范围的索引时,它会返回 None
而不会出现紧急情况。这在正常情况下偶尔会发生访问超出向量范围的元素时特别有用。您的代码将具有逻辑来处理 Some(element)
或 None
。例如,索引可能作为合约方法参数传递。如果传递的参数过大,方法 get
将返回一个 None
值,合约方法随后可以决定在发生这种情况时回滚,或者返回一个有意义的错误,告诉用户当前向量中有多少项,并为其提供另一次传递有效值的机会。
要依次访问向量中的每个元素,我们将使用 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;
}
}
再次强调,这与遍历 Vec<T>
元素的方法非常相似,其中我们使用 len
方法返回向量的长度。我们还调用 unwrap
方法来提取 get
返回的 Option
,然后调用 read()
实际读取存储的值。我们知道 unwrap
不会失败(即不会导致回滚),因为传递给 get
的每个索引 i
都已知小于向量的长度。
存储向量,就像 Vec<T>
一样,只能存储相同类型的值。与我们在 使用枚举存储多种类型 部分为 Vec<T>
所做的类似,我们可以定义一个枚举,其变体将保存不同的值类型,并且所有枚举变体都将被视为相同的类型:即枚举类型。示例如下:
enum TableCell {
Int: u64,
B256: b256,
Boolean: bool,
}
然后,我们可以在 storage
块中声明一个 StorageVec
来保存该枚举,因此,最终会保存不同的类型:
row: StorageVec<TableCell> = StorageVec {},
现在,我们可以将不同的枚举变体推送到 StorageVec
,如下所示:
#[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));
}
现在我们已经讨论了一些使用存储向量的常见方法,请务必查看标准库中定义的 StorageVec<T>
的所有许多有用方法的 API 文档。目前,这些可以在 source code for StorageVec<T>
中找到。例如,除了 push
之外,pop
方法会移除并返回最后一个元素,remove
方法会移除并返回向量中某个选择的索引处的元素,insert
方法会在向量中的某个选择的索引处插入元素等。
可以像这样嵌套存储向量:
nested_vec: StorageVec<StorageVec<u64>> = StorageVec {},
然后可以这样访问嵌套向量:
#[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());
}