十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Sway StorageMap 存储映射实战:键值对持久化存储的声明、读写与底层原理

Sway StorageMap 存储映射实战:键值对持久化存储的声明、读写与底层原理 Sway StorageMap 存储映射实战键值对持久化存储的声明、读写与底层原理【免费下载链接】sway Empowering everyone to build reliable and efficient smart contracts.项目地址: https://gitcode.com/GitHub_Trending/sw/sway导读StorageMap是 Sway 标准库提供的一种持久化键值对key-value存储结构常被形象地称为哈希表hash table。在 Sway 智能合约开发中它是实现「地址 → 余额」「用户 → 权限」这类映射关系的核心工具数据被永久写入链上存储storage并可通过键在常数时间O(1)内完成定位。本文将基于本仓库 Sway 参考文档storage-map.md及其配套示例系统讲解StorageMap的声明、读取、写入三大操作并结合标准库源码剖析其底层存储槽计算、Option 语义与完整方法集帮助你写出可复用的映射式合约存储逻辑。StorageMap 是什么以键定位值的持久化哈希表StorageMap是一种将值v与键k关联起来的数据结构。键k被用来在存储storage中定位值v所在的位置。与传统内存哈希表不同Sway 的StorageMap的数据是持久化的——它直接写入链上存储槽而不是堆heap内存。哈希表最核心的收益在于查找效率无论值位于表的哪个位置定位该值所需的计算量都是常数级别的即时间复杂度为O(1)。在链上合约场景中这意味着读取某个用户余额或查询某个标识对应的状态不会因为数据量的增长而线性变慢。StorageMap的一个显著特点是高度的灵活性——它使用泛型generics同时约束键k和值v的类型。Sway 语言层面的泛型机制可参考 generics 文档。唯一的前提约束是k与v都必须是单个值single value。这个「单个值」并不等同于单一基础类型——值v可以是结构体、元组、数组等复合类型。因此如果你需要用一个复杂结构作为键或值只需要把数据包装wrap进一个单一类型即可StorageMap仍然能够正常工作。从源码结构看标准库中StorageMap定义为pub struct StorageMapK, V {}storage_map.sw是一个零尺寸zero-sized存储类型可以嵌套在其他存储类型内部使用例如StorageMapK, StorageMap这样的嵌套映射。声明 StorageMap泛型键值对与 prelude 导入无需手动导入StorageMap类型被包含在标准库的 prelude 中因此声明时不需要额外use导入。如果在后续代码中需要使用msg_sender()来获取调用者身份则需要显式导入相关路径示例中通过use std::hash::*;引入哈希与发送者相关能力见 main.sw。初始化写法StorageMap在storage块中按初始化指南的方式声明每个变量被命名、关联类型并给出默认值。示例代码如下完整文件见 storage_map/src/main.swstorage { // k Identity, v u64 balance: StorageMapIdentity, u64 StorageMap::Identity, u64 {}, // k (Identity, u64), v bool user: StorageMap(Identity, u64), bool StorageMap::(Identity, u64), bool {}, }示例中声明了两个 storage 变量balance以单个值作为键映射类型为StorageMapIdentity, u64即用Identity调用者身份映射到u64余额。user将两个值包装成一个元组(Identity, u64)作为键映射类型为StorageMap(Identity, u64), bool即用「身份 用户编号」的组合唯一标识一个用户及其状态。第二行正体现了上文提到的「复杂键需包装为单一类型」的设计把两个字段打包进一个元组就能组合成复合键。声明语法要求显式写出初始化表达式StorageMap::K, V {}这是空映射的字面量构造方式。从存储中读取.get(key)与 Option 语义基本用法从存储中检索数据通过.get(key)方法完成指定要读取的 storage 变量在末尾追加.get()并在括号内传入想要检索的数据的键。示例代码main.sw#[storage(read)] fn reading_from_storage(id: u64) { let user storage.user.get((msg_sender().unwrap(), id)).read(); }在这个示例中合约把调用者的Identity通过msg_sender().unwrap()取得与调用者提供的id包装成一个元组作为复合键传入.get()从而读取storage.user中该键对应的值。返回Option配合unwrap_or兜底.get(key)返回一个Option如果映射中不存在key对应的值get会返回None。因此读取后必须处理可能为空的情况。参考文档明确指出示例合约通过调用unwrap_or来处理返回的Option——当映射user中没有该键的条目时将用户状态置为零值兜底。需要特别说明的是.get()返回的是StorageKeyV对其调用.read()才能得到最终的OptionV在需要默认值时可在read()结果上继续调用unwrap_or(default)这是文档所描述的「将user设为零」的完整链路。完整的读取-兜底模式可写为let user storage.user.get((msg_sender().unwrap(), id)).read().unwrap_or(false);存储纯度标注读取函数必须标注#[storage(read)]属性表明该函数仅读取存储、不修改存储。这与读写指南中「处理存储时必须使用 storage 注解指示函数纯度」的要求一致。Sway 编译器会依据注解执行纯度检查防止在只读上下文中意外写入存储。写入存储.insert(key, value)与读改写模式基本用法写入存储与读取类似区别在于使用的方法变成了.insert(key, value)。示例代码main.sw#[storage(read, write)] fn writing_to_storage() { let balance storage.balance.get(msg_sender().unwrap()).read(); storage.balance.insert(msg_sender().unwrap(), balance 1); }这个示例实现了一个典型的「读-改-写」read-modify-write流程先通过.get(msg_sender().unwrap()).read()读取当前调用者的余额将其加 1再通过.insert(msg_sender().unwrap(), balance 1)把新余额写回storage.balance。因为insert会修改存储所以函数需要标注#[storage(read, write)]同时声明了读取与写入两种纯度。关于默认值的一个细节示例代码第 23 行直接对.read()的结果Optionu64执行 1从源码可见该示例默认调用者的余额已存在即此前已insert过。在实际生产代码中若映射可能尚未初始化建议先通过unwrap_or(0)提供默认值再执行算术运算以避免对None的处理缺失#[storage(read, write)] fn safe_increment() { let current storage.balance.get(msg_sender().unwrap()).read().unwrap_or(0); storage.balance.insert(msg_sender().unwrap(), current 1); }标准库源码剖析存储槽如何由键计算而来StorageMap的实现位于标准库 storage_map.sw它建立在更底层的StorageKey与storage_api抽象之上模块导出见 storage.sw。理解其内部机制有助于写出更高效的存储代码。键到存储槽的哈希推导StorageMap中键k到存储槽slot的映射不是线性地址而是通过哈希函数推导而来。核心逻辑在get_slot_key中storage_map.swfn get_slot_key(self, key: K) - b256 { sha256((STORAGE_MAP_DOMAIN, key, self.field_id())) }使用sha256对「域前缀STORAGE_MAP_DOMAIN 键 字段标识field_id」的元组求哈希得到b256类型的存储槽地址STORAGE_MAP_DOMAIN取值为1u8storage_map.sw。源码注释说明为了防止用户输入键的哈希原像与编译器为 storage 字段生成的原像相撞标准库为存储映射域添加一个单字节前缀从而在密钥空间中隔离不同的存储域由于同一个field_id的哈希输入固定同一键在链上总是映射到同一存储槽因此StorageMap天然具备确定性——这正是合约状态一致性的基础。get返回StorageKeyVget(key)的返回类型是StorageKeyVstorage_map.sw它描述「无论该位置是否真的存有值键对应的值在存储中的位置」。真正读取值需要通过StorageKey上的.read()方法完成storage_key.swpub fn read(self) - T { read_quads::T(self.slot, self.offset).unwrap() }read()内部通过read_quads读取对应存储槽并返回OptionT解包后的值——若槽位为空则触发回退revert。这就是文档中「get 返回Option」的底层来源Option语义由底层存储读取在「槽未写入」时产生None的机制承载。而try_read()storage_key.sw则提供不触发回退、直接返回OptionT的读取方式适合需要安全兜底的场景。写入与清除insert/remove/try_insert标准库为StorageMap提供了比文档示例更完整的操作集默认experimental_dynamic_storage false分支storage_map.swinsert(key, value)通过write_quads::V(key, 0, value)将值写入由get_slot_key推导出的存储槽storage_map.sw。注解#[storage(read, write)]表明写入可能需要先读取部分数据以覆盖其文档注明当值占满整个槽时读次数为 0否则为 1用于读取将被部分覆盖的旧数据写次数恒为 1。remove(key)返回booltrue表示该键此前存有值通过clear_quads清除storage_map.sw。try_insert(key, value)仅当键尚无值时插入返回ResultV, StorageMapErrorV若键已存在返回StorageMapError::OccupiedError(旧值)并不覆盖原值storage_map.sw。这是实现「首次创建、禁止覆盖」类业务如一次性注册的利器。当experimental_dynamic_storage true时标准库提供另一套基于write_slot/read_slot的实现storage_map.sw方法名略有差异如remove_existed语义面向动态存储实验特性。完整方法速查方法签名要点作用存储访问get(key)返回StorageKeyV定位键对应值的存储位置配合.read()/.try_read()取值0 次仅计算位置insert(key, value)无返回值写入/覆盖键值对写 1若值未占满槽则读 1remove(key)返回bool清除键对应值返回是否存在旧值清除 1try_insert(key, value)返回ResultV, StorageMapErrorV键不存在时才写入否则返回旧值读 1写入时再写 1实践要点与组合建议复合键的包装StorageMap的键、值都必须是单一类型。需要多字段键时用元组如(Identity, u64)、结构体或数组包裹同样结构体、元组、数组等复合类型都可以直接作为值v存储。Option 必处理get(key)在键不存在时返回None。读取后使用unwrap_or(default)提供默认值或使用match显式分支处理避免直接解包导致合约回退。纯度注解不可缺只读函数标注#[storage(read)]读改写函数标注#[storage(read, write)]写入专用函数可标注#[storage(write)]。注解与storage.变量名语法是 Sway 存储安全的两道闸门。优先try_insert实现幂等写入对于「账户只能注册一次」等语义用try_insert天然返回冲突旧值避免「先 get 再 insert」的竞态窗口。与其他存储工具搭配StorageMap属于标准库存储工具家族其余还包括持久化向量StorageVecstorage-vec.md以及直接操作存储槽的store()/get()store-get.md总览见 libraries/index.md。映射适合「键 → 值」关系向量适合按索引存取裸槽操作则适合底层精细控制应按业务形态选择。配套示例可运行本文全部代码取自仓库示例 storage_map/src/main.sw其Forc.toml位于 storage_map/Forc.toml可直接在 Sway 合约工程中编译验证。小结StorageMap是 Sway 合约中最常用的持久化数据结构之一它以O(1)的常数时间完成键值定位通过泛型同时支持基础类型与复合类型结构体、元组、数组作为键或值仅需将复杂数据包装为单一类型。声明上StorageMap位于 prelude 无需导入在storage块中按StorageMap::K, V {}初始化读取使用.get(key)返回Option可用unwrap_or兜底写入使用.insert(key, value)并配合#[storage(read)]/#[storage(read, write)]纯度注解。底层实现上每个键通过sha256((STORAGE_MAP_DOMAIN, key, field_id))推导出确定性的存储槽地址标准库还提供了remove、try_insert等更丰富的原子操作。掌握这套声明、读写与原理你就能在合约中稳健地管理一切「以键查值」的持久化状态。【免费下载链接】sway Empowering everyone to build reliable and efficient smart contracts.项目地址: https://gitcode.com/GitHub_Trending/sw/sway创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表