存储映射器
原文:https://docs.elrond.com/developers/developer-reference/storage-mappers
Rust 框架提供了您可以使用的各种存储映射器。决定在每种情况下使用哪一种对性能至关重要。在描述每个映射器后将有一个比较部分。
注意:所有存储映射器都支持额外的键参数。
通用绘图仪
单值映射器
存储单个值。示例:
fn single_value(&self) -> SingleValueMapper<Type>;
fn single_value_with_single_key_arg(&self, key_arg: Type1) -> SingleValueMapper<Type2>;
fn single_value_with_multi_key_arg(&self, key_arg1: Type1, key_arg2: Type2) -> SingleValueMapper<Type3>;
请记住,没有办法遍历所有的key_arg
,所以如果您需要这样做,请考虑使用另一个映射器。
可用方法:
搞定
fn get() -> Type
从存储中读取值,并将其反序列化到给定的Type
。对于数值类型和向量类型,这将返回空存储的默认值,即分别为 0 和空 vec。对于自定义结构,如果试图从空存储中读取,这将发出错误信号。
设定
fn set(value: &Type)
将存储值设置为提供的value
参数。对于基锈数值类型,不需要引用。
是 _ 空
fn is_empty() -> bool
返回true
是存储条目为空。通常在存储结构类型时使用,以防止在get()
上崩溃。
set _ if _ empty
fn set_if_empty(value: &Type)
仅当该值的存储当前为空时,才设置该值。通常在#init 函数中使用,以避免在合约升级时覆盖值。
明朗
fn clear()
清除条目。
更新
fn update<R, F: FnOnce(&mut Type) -> R>(f: F) -> R
将闭包作为参数,将该闭包应用于当前存储的值,保存新值,并返回给定闭包可能返回的任何值。示例:
增加一个值:
fn my_value(&self) -> SingleValueMapper<BigUint>;
self.my_value().update(|val| *val += 1);
修改结构的字段:
pub struct MyStruct {
pub field_1: u64,
pub field_2: u32
}
fn my_value(&self) -> SingleValueMapper<MyStruct>;
self.my_value().update(|val| val.field1 = 5);
从闭包返回值:
fn my_value(&self) -> SingleValueMapper<BigUint>;
let new_val = self.my_value().update(|val| {
*val += 1;
*val
});
raw _ byte _ length
fn raw_byte_length() -> usize
返回存储值的原始字节长度。这个应该很少用。
向量映射器
存储相同类型的元素,每个元素都有自己的存储键。允许通过索引访问所述项目。请记住,对于 VecMapper,索引从 1 开始。示例:
fn my_vec(&self) -> VecMapper<Type>;
fn my_vec_with_args(&self, arg: Type1) -> VecMapper<Type2>;
可用方法:
推
fn push(elem: &T)
将元素存储在索引len
处,之后递增len
。
搞定
fn get(index: usize) -> Type
获取特定索引处的元素。有效索引是 1 到len
,包括两端。试图从无效索引中读取将发出错误信号。
设定
fn set(index: usize, value: &Type)
设置给定索引处的元素。索引必须在 1 到len
的范围内。
清除 _ 条目
fn clear_entry(index: usize)
清除给定索引处的条目。这不会减少长度。
是 _ 空
fn is_empty() -> bool
如果映射器没有存储元素,则返回true
。
len
fn len() -> usize
返回存储在映射器中的项目数。
扩展 _ 从 _ 切片
fn extend_from_slice(slice: &[Type])
将给定切片中的所有元素推到映射器的末尾。比push
的手动for
更有效,因为内部长度只被读取和更新一次。
互换 _ 移除
fn swap_remove(index: usize)
移除index
处的元素,将最后一个元素移动到index
,并将len
减 1。没有办法移除元素并保持顺序。
明朗
fn clear()
从映射器中清除所有元素。这个函数可能会因为大量收集而耗尽资源。
fn iter() -> Iter<Type>
提供所有元素的迭代器。
设置映射器
存储一组值,不允许重复。它还提供了检查集合中是否已经存在某个值的方法。
除非你需要保持元素的顺序,否则考虑使用UnorderedSetMapper
或WhitelistMapper
来代替,因为它们更有效。
示例:
fn my_set(&self) -> SetMapper<Type>;
可用方法:
插入
fn insert(value: Type) -> bool
将值插入集合中。如果项目已经存在,则返回false
。
清除
fn remove(value: &Type)
从集合中移除值。如果集合不包含值,则返回false
。
包含
fn contains(value: &Type) -> bool
如果映射器包含给定值,则返回true
。
是 _ 空
fn is_empty() -> bool
如果映射器没有存储元素,则返回true
。
len
fn len() -> usize
返回存储在映射器中的项目数。
明朗
fn clear()
从映射器中清除所有元素。这个函数可能会因为大量收集而耗尽资源。
fn iter() -> Iter<Type>
返回所有存储元素的迭代器。
unordered set mapper
与 SetMapper 相同,但不保证项目的顺序。比SetMapper
更高效,除非需要维护顺序,否则应该用。在内部,UnorderedSetMapper
使用一个VecMapper
来存储元素,此外,它还存储每个元素的索引以提供 O(1) contains
。
示例:
fn my_set(&self) -> UnorderedSetMapper<Type>;
可用方法:
UnorderedSetMapper
包含与SetMapper
相同的方法,唯一的区别是项目移除。没有remove
,我们只有swap_remove
可用。
互换 _ 移除
fn swap_remove(value: &Type) -> bool
使用内部VecMapper
的 swap_remove 方法移除元素。此外,它用移除值的索引覆盖最后一个元素的存储索引。如果元素不在集合中,则返回false
。
白名单映射器
存储项目的白名单。没有提供任何迭代元素的方法,所以如果您需要迭代元素,请使用UnorderedSetMapper
来代替。在内部,如果每个项目被列入白名单,这个映射器只是在存储中为它们存储一个标志。
示例:
fn my_whitelist(&self) -> WhitelistMapper<Self::Api, Type>
可用方法:
添加
fn add(value: &Type)
将值添加到白名单中。
清除
fn remove(value: &Type)
从白名单中删除该值。
包含
fn contains(value: &Type) -> bool
如果映射器包含给定值,则返回true
。
要求 _ 白名单
fn require_whitelisted(value: &Type)
如果项目不在白名单中,将发出错误信号。否则什么也不做。
linked list mapper
存储一个链接列表,允许快速插入/删除元素,以及迭代整个列表的可能性。
示例:
fn my_linked_list(&self) -> LinkedListMapper<Type>
可用方法:
是 _ 空
fn is_empty() -> bool
如果映射器没有存储元素,则返回true
。
len
fn len() -> usize
返回存储在映射器中的项目数。
明朗
fn clear()
从映射器中清除所有元素。这个函数可能会因为大量收集而耗尽资源。
fn iter() -> Iter<Type>
返回所有存储元素的迭代器。
ITER _ from _ node _ id
fn iter_from_node_id(node_id: u32) -> Iter<Type>
从给定的node_id
开始返回一个迭代器。在多个 SC 调用上拆分迭代时很有用。
fn front() -> Option<LinkedListNode<Type>>
fn back() -> Option<LinkedListNode<Type>>
如果列表不为空,返回第一个/最后一个元素,否则返回None
。一个LinkedListNode
具有以下格式:
pub struct LinkedListNode<Type> {
value: Type,
node_id: u32,
next_id: u32,
prev_id: u32,
}
impl<Type> LinkedListNode<Type> {
pub fn get_value_cloned(&self) -> Type {
self.value.clone()
}
pub fn get_value_as_ref(&self) -> &Type {
&self.value
}
pub fn into_value(self) -> Type {
self.value
}
pub fn get_node_id(&self) -> u32 {
self.node_id
}
pub fn get_next_node_id(&self) -> u32 {
self.next_id
}
pub fn get_prev_node_id(&self) -> u32 {
self.prev_id
}
}
【流行 _ 前沿/流行 _ 后沿】
fn pop_front(&mut self) -> Option<LinkedListNode<Type>>
fn pop_back(&mut self) -> Option<LinkedListNode<Type>>
从列表中移除并返回第一个/最后一个元素。
【推 _ 后/推 _ 后
pub fn push_after(node: &mut LinkedListNode<Type>, element: Type) -> Option<LinkedListNode<Type>>
pub fn push_before(node: &mut LinkedListNode<Type>, element: Type) -> Option<LinkedListNode<Type>>
将给定的element
插入到列表中给定的node
之后/之前。如果插入成功,返回新插入的节点,否则返回None
。
推送 _ 之后 _ 节点 _id/推送 _ 之前 _ 节点 _ id
pub fn push_after_node_id(node_id: usize, element: Type) -> Option<LinkedListNode<Type>>
pub fn push_before_node_id(node_id: usize, element: Type) -> Option<LinkedListNode<Type>>
与上面的方法相同,但是使用 node_id 而不是完整的节点结构。
【前推/后推】
fn push_front(element: Type)
fn push_back(element: Type)
将给定的element
推到列表的前面/后面。可以看作是push_before_node_id
和push_after_node_id
的专门版本。
设置 _ 节点 _ 值
fn set_node_value(mut node: LinkedListNode<Type>, new_value: Type)
如果列表中存在节点,则设置该节点的值。
设置 _ 节点 _ 值 _by_id
fn set_node_value_by_id(node_id: usize, new_value: Type)
与上面的方法相同,但是使用 node_id 而不是完整的节点结构。
删除 _ 节点
fn remove_node(node: &LinkedListNode<Type>)
从列表中移除节点(如果存在)。
删除 _ 节点 _by_id
fn remove_node(node_id: usize)
与上面的方法相同,但是使用 node_id 而不是完整的节点结构。
fn iter() -> Iter<Type>
返回所有存储元素的迭代器。
ITER _ from _ node _ id
fn iter_from_node_id(node_id: u32) -> Iter<Type>
从给定的node_id
开始返回一个迭代器。在多个 SC 调用上拆分迭代时很有用。
地图绘制者
存储(键,值)对,同时还允许对键进行迭代。这是使用起来最昂贵的映射器,所以确保你真的需要使用它。
示例:
fn my_map(&self) -> MapMapper<KeyType, ValueType>
可用方法:
是 _ 空
fn is_empty() -> bool
如果映射器没有存储元素,则返回true
。
len
fn len() -> usize
返回存储在映射器中的项目数。
包含 _key
fn contains_key(k: &KeyType) -> bool
如果映射器包含给定的键,则返回true
。
搞定
fn get(k: &KeyType) -> Option<ValueType>
如果键存在,则返回Some(value)
。如果映射中不存在关键字,则返回None
。
插入
fn insert(k: KeyType, v: ValueType) -> Option<V>
将给定的键、值对插入到映射中,如果键已经存在,则返回Some(old_value)
。
清除
fn remove(k: &KeyType) -> Option<ValueType>
从映射中移除键和相应的值,并返回值。如果键不在映射中,则返回None
。
键/值/iter
fn keys() -> Keys<KeyType>
fn values() -> Values<ValueType>
fn iter() -> Iter<KeyType, ValueType>
分别在所有键、值和(键、值)对上提供迭代器。
专业制图师
FungibleTokenMapper
存储一个代币标识符(像一个SingleValueMapper<TokenIdentifier>
)并提供将这个代币 ID 直接用于最常见的 API 函数的方法。请注意,如果之前没有发出代币,大多数方法调用都会失败。
示例:
fn my_token_id(&self) -> FungibleTokenMapper<Self::Api>
可用方法:
发布/发布 _ 和 _ 设置 _ 所有 _ 角色
fn issue(issue_cost: BigUint, token_display_name: ManagedBuffer, token_ticker: ManagedBuffer,initial_supply: BigUint, num_decimals: usize, opt_callback: Option<CallbackClosure<SA>>) -> !
fn issue_and_set_all_roles(issue_cost: BigUint, token_display_name: ManagedBuffer, token_ticker: ManagedBuffer,initial_supply: BigUint, num_decimals: usize, opt_callback: Option<CallbackClosure<SA>>) -> !
颁发新的可替换代币。在我写这篇文章的时候是 0.05 EGLD (500000000000000),但是因为它在过去发生了变化,我们让它作为一个参数,以防它在将来再次发生变化。
此映射器只允许一个问题,因此尝试发出多个类型将会发出错误信号。
opt_callback
是一个可选的自定义回拨,您可以使用它来发出呼叫。我们建议使用默认回调。为此,您需要在货物中导入 elrond-wasm-modules。toml:
[dependencies.elrond-wasm-modules]
version = "0.31.1"
注:在撰写本文时,当前发布的 elrond-wasm 版本是 0.31.1,如有必要,请升级。
然后,您应该在合约中导入DefaultCallbacksModule
:
#[elrond_wasm::contract]
pub trait MyContract: elrond_wasm_modules::default_issue_callbacks::DefaultIssueCallbacksModule {
/* ... */
}
另外,为opt_callback
传递None
。
注意“从不”类型-> !
作为该函数的返回类型。这意味着该函数将终止执行并启动 issue 异步调用,因此该调用之后的任何代码都不会被执行。
或者,如果您想发布并为 SC 设置所有角色,您可以使用issue_and_set_all_roles
方法。
薄荷
fn mint(amount: BigUint) -> EsdtTokenPayment<Self::Api>
使用ESDTLocalMint
内置函数为存储的代币 ID 生成amount
代币。返回一个支付结构,包含代币 ID 和给定的金额。
薄荷 _ 和 _ 发送
fn mint_and_send(to: &ManagedAddress, amount: BigUint) -> EsdtTokenPayment<SA>
与上面的方法相同,但是也将生成的代币发送到给定的地址。
烧
fn burn(amount: &BigUint)
使用ESDTLocalBurn
内置函数刻录amount
代币。
获取 _ 平衡
fn get_balance() -> BigUint
获取 SC 对代币的当前余额。
与FungibleTokenMapper
类似,但用于 NFT、SFT 和元 ESDT 代币。
发布/发布 _ 和 _ 设置 _ 所有 _ 角色
fn issue(token_type: EsdtTokenType, issue_cost: BigUint, token_display_name: ManagedBuffer, token_ticker: ManagedBuffer,initial_supply: BigUint, num_decimals: usize, opt_callback: Option<CallbackClosure>) -> !
fn issue_and_set_all_roles(token_type: EsdtTokenType, issue_cost: BigUint, token_display_name: ManagedBuffer, token_ticker: ManagedBuffer,initial_supply: BigUint, num_decimals: usize, opt_callback: Option<CallbackClosure>) -> !
与前面的 issue 函数相同,但也采用一个EsdtTokenType
enum 作为参数,以决定要发布哪种类型的代币。可接受的值有EsdtTokenType::NonFungible
、EsdtTokenType::SemiFungible
和EsdtTokenType::Meta
。
NFT _ create/NFT _ create _ named
fn nft_create<T: TopEncode>(amount: BigUint, attributes: &T) -> EsdtTokenPayment<Self::Api>
fn nft_create_named<T: TopEncode>(amount: BigUint, name: &ManagedBuffer, attributes: &T) -> EsdtTokenPayment<Self::Api>
创建一个 NFT(可以选择显示name
)并返回代币 ID、创建的代币的 nonce 和支付结构中的给定金额。
NFT _ create _ and _ send/NFT _ create _ and _ send _ named
fn nft_create_and_send<T: TopEncode>(to: &ManagedAddress, amount: BigUint, attributes: &T,) -> EsdtTokenPayment<Self::Api>
fn nft_create_and_send_named<T: TopEncode>(to: &ManagedAddress, amount: BigUint, name: &ManagedBuffer, attributes: &T,) -> EsdtTokenPayment<Self::Api>
与上面的方法相同,但是也将创建的代币发送到提供的地址。
NFT _ 添加 _ 数量
fn nft_add_quantity(token_nonce: u64, amount: BigUint) -> EsdtTokenPayment<Self::Api>
添加给定代币随机数的数量。这只能在以前使用过nft_create
函数之一并且 SC 为给定的 nonce 持有至少 1 个代币的情况下使用。
NFT _ 添加 _ 数量 _ 发送
fn nft_add_quantity_and_send(to: &ManagedAddress, token_nonce: u64, amount: BigUint) -> EsdtTokenPayment<Self::Api>
与上面的方法相同,但是也将代币发送到提供的地址。
NFT _ burn
fn nft_burn(token_nonce: u64, amount: &BigUint)
燃烧给定随机数的amount
代币。
get _ all _ token _ data
fn get_all_token_data(token_nonce: u64) -> EsdtTokenData<Self::Api>
获取给定 nonce 的所有代币数据。SC 必须拥有给定的 nonce,该函数才能工作。
EsdtTokenData
包含以下字段:
pub struct EsdtTokenData<M: ManagedTypeApi> {
pub token_type: EsdtTokenType,
pub amount: BigUint<M>,
pub frozen: bool,
pub hash: ManagedBuffer<M>,
pub name: ManagedBuffer<M>,
pub attributes: ManagedBuffer<M>,
pub creator: ManagedAddress<M>,
pub royalties: BigUint<M>,
pub uris: ManagedVec<M, ManagedBuffer<M>>,
}
获取 _ 平衡
fn get_balance(token_nonce: u64) -> BigUint
获取给定代币现时的 SC 余额。
获取 _ 代币 _ 属性
fn get_token_attributes<T: TopDecode>(token_nonce: u64) -> T
获取给定标记 nonce 的属性。SC 必须拥有给定的 nonce,该函数才能工作。
FungibleTokenMapper 和 NonFungibleTokenMapper 的常用函数
这两种映射器的工作方式相似,所以有些函数对两者有相同的实现。
是 _ 空
fn is_empty() -> bool
如果代币 ID 尚未设置,则返回true
。
get _ token _ id
fn get_token_id() -> TokenIdentifier<SA>
获取存储的代币 ID。
set _ token _ id
fn set_token_id(token_id: &TokenIdentifier)
手动设置此映射器的代币 ID。这个只能用一次,以后不能覆盖。如果代币是以前颁发的,这将失败,因为代币 ID 是自动设置的。
要求 _ 相同 _ 代币/要求 _ 全部 _ 相同 _ 代币
fn require_same_token(expected_token_id: &TokenIdentifier<SA>)
fn require_all_same_token(payments: &ManagedVec<EsdtTokenPayment<Self::Api>>)
如果提供的代币 ID 参数与存储的代币不同,将发出错误信号。在#[payable]
方法中很有用,当你只想要这个代币作为支付时。
设置 _ 本地 _ 角色
fn set_local_roles(roles: &[EsdtLocalRole], opt_callback: Option<CallbackClosure>) -> !
为代币设置提供的本地角色。默认情况下,此调用不使用回调,但是如果您愿意,可以提供自定义回调。
如果使用issue_and_set_all_roles
发布,则不需要调用该函数。
与 issue 函数相同,这将在被调用时终止执行。
设置 _ 本地 _ 角色 _ 地址
fn set_local_roles_for_address(address: &ManagedAddress, roles: &[EsdtLocalRole], opt_callback: Option<CallbackClosure>) -> !
类似于前面的功能,但是为特定地址而不是 SC 地址设置角色。
uniqueid mapper
一个特殊的映射器,保存从 1 到 N 的值,具有以下属性:如果mapper[i] == i
,则实际上不存储任何内容。
这使得映射器初始化为 O(1)而不是 O(N)。顾名思义,当您想要一个可用 id 的列表时,这非常有用。
id 和索引都是usize
。
注意:如果你想要一个内存版本,你可以使用框架提供的SparseArray
类型。
示例:
fn my_id_mapper(&self) -> UniqueIdMapper<Self::Api>
可用方法:
fn set_initial_len(&mut self, len: usize)
设置初始映射器长度,即N
。长度只能设置一次。
是 _ 空
fn is_empty() -> bool
如果映射器没有存储元素,则返回true
。
len
fn len() -> usize
返回存储在映射器中的项目数。
搞定
fn get(index: usize) -> usize
获取给定索引的值。如果条目为空,那么根据映射器的属性,返回index
。
设定
fn set(&mut self, index: usize, id: usize)
设置给定索引处的值。如果维护空条目,映射器的内部属性为mapper[i] == i
。
互换 _ 移除
fn swap_remove(index: usize) -> usize
移除给定index
处的 ID 并返回它。另外,index
处的值现在被设置为映射中最后一个条目的值。长度减少 1。
fn iter() -> Iter<usize>
为所有的 id 提供一个迭代器。
不同制图者之间的比较
单值映射器 vs 旧存储 _ 设置/存储 _ 获取对
SingleValueMapper
和老派的 setter/getter 没什么区别。其实,SingleValueMapper
基本上就是storage_set
、storage_get
、storage_is_empty
和storage_clear
的组合。鼓励使用SingleValueMapper
,因为它更紧凑,并且没有性能损失(例如,如果您从不使用is_empty()
,编译器将会删除该代码)。
单值映射器 vs 向量映射器
存储ManagedVec<T>
有两种方式:
#[storage_mapper("my_vec_single)]
fn my_vec_single(&self) -> SingleValueMapper<ManagedVec<T>>
#[storage_mapper("my_vec_mapper)]
fn my_vec_mapper(&self) -> VecMapper<T>;
这两种方法各有千秋。SingleValueMapper
将所有元素连接起来并存储在一个键下,而VecMapper
将每个元素存储在不同的键下。这也意味着SingleValueMapper
对每个元素使用嵌套编码,而VecMapper
使用顶层编码。
在以下情况下使用SingleValueMapper
:
- 每次使用时都需要读取整个数组
- 该阵列预计长度较小
在以下情况下使用VecMapper
:
- 你只需要读取数组的一部分
T
的顶层编码比T
的嵌套编码效率高得多(例如:u64
)
vecmappeal vs setmapper
SetMapper
的主要用途是存储地址、代币 id 等的白名单。代币 ID 白名单可以通过以下两种方式存储:
#[storage_mapper("my_vec_whitelist)]
fn my_vec_whitelist(&self) -> VecMapper<TokenIdentifier>
#[storage_mapper("my_set_mapper)]
fn my_set_mapper(&self) -> SetMapper<TokenIdentifier>;
这可能看起来非常相似,但是使用VecMapper
的含义对潜在的天然气成本非常有害。在 O(n)中检查一个条目在VecMapper
中是否存在,每次迭代都需要一次新的存储读取!最坏的情况是代币 ID 不在白名单中,整个 Vec 被读取。
SetMapper
比这有效得多,因为它提供了对 O(1)中的值的检查。然而,这并不是没有代价的。这是存储如何寻找具有两个元素的SetMapper
(这个片段取自 mandos 测试):
"str:tokenWhitelist.info": "u32:2|u32:1|u32:2|u32:2",
"str:tokenWhitelist.node_idEGLD-123456": "2",
"str:tokenWhitelist.node_idETH-123456": "1",
"str:tokenWhitelist.node_links|u32:1": "u32:0|u32:2",
"str:tokenWhitelist.node_links|u32:2": "u32:1|u32:0",
"str:tokenWhitelist.value|u32:2": "str:EGLD-123456",
"str:tokenWhitelist.value|u32:1": "str:ETH-123456"
A SetMapper
使用 3 * N + 1 个存储条目,其中 N 是元素的数量。检查一个元素非常容易,因为映射器唯一要做的事情就是检查提供的代币 ID 的node_id
条目。
即便如此,对于这种特殊情况,SetMapper
比VecMapper
好得多。
vecmapter vs linked list mapper
LinkedListMapper
可以看作是对VecMapper
的特殊化。它只允许在列表的任意一端插入/删除,称为推入/弹出。它还具有存储效率,因为它只需要 2 * N + 1 个存储条目。这种映射器的存储如下所示:
"str:list_mapper.node_links|u32:1": "u32:0|u32:2",
"str:list_mapper.node_links|u32:2": "u32:1|u32:0",
"str:list_mapper.value|u32:1": "123",
"str:list_mapper.value|u32:2": "111",
"str:list_mapper.info": "u32:2|u32:1|u32:2|u32:2"
这是很少使用的映射器之一,因为它的用途非常具体,但是如果您需要存储队列,它非常有用。
SingleValueMapper vs MapMapper
信不信由你,很多时候,MapMapper
甚至都不需要,可以简单的用一个SingleValueMapper
代替。例如,假设您想为每个地址存储一个 ID。使用MapMapper
可能很有诱惑力,它看起来像这样:
#[storage_mapper("address_id_mapper")]
fn address_id_mapper(&self) -> MapMapper<ManagedAddress, u64>;
这可以替换为以下SingleValueMapper
:
#[storage_mapper("address_id_mapper")]
fn address_id_mapper(&self, address: &ManagedAddress) -> SingleValueMapper<u64>;
它们都提供(几乎)相同的功能。不同之处在于,SingleValueMapper
没有提供遍历所有键的方法,也就是说,本例中的地址,但是它的效率也提高了 4-5 倍。
除非您需要迭代所有条目,否则应该避免使用MapMapper
,因为这是最昂贵的映射器。它使用 4 * N + 1 个存储条目。MapMapper
的存储看起来像这样:
"str:map_mapper.node_links|u32:1": "u32:0|u32:2",
"str:map_mapper.node_links|u32:2": "u32:1|u32:0",
"str:map_mapper.value|u32:1": "123",
"str:map_mapper.value|u32:2": "111",
"str:map_mapper.node_id|u32:123": "1",
"str:map_mapper.node_id|u32:111": "2",
"str:map_mapper.mapped|u32:123": "456",
"str:map_mapper.mapped|u32:111": "222",
"str:map_mapper.info": "u32:2|u32:1|u32:2|u32:2"
请记住,所有的映射器都可以有尽可能多的主关键字的附加参数。例如,您可以为每个用户对设置一个VecMapper
,如下所示:
#[storage_mapper("list_per_user_pair")]
fn list_per_user_pair(&self, first_addr: &ManagedAddress, second_addr: &ManagedAddress) -> VecMapper<T>;
根据您的情况使用正确的映射器可以大大降低气体成本和复杂性,因此请始终记得仔细评估您的使用案例。