Advanced Storage in Sway:嵌套存储、存储命名空间与手动存储管理

发布时间:2026/9/12 9:48:55
Advanced Storage in Sway:嵌套存储、存储命名空间与手动存储管理 Advanced Storage in Sway嵌套存储、存储命名空间与手动存储管理【免费下载链接】sway Empowering everyone to build reliable and efficient smart contracts.项目地址: https://gitcode.com/GitHub_Trending/sw/sway本指南基于 Sway 官方文档的《Advanced Storage》章节深入讲解StorageKey机制下的嵌套存储集合StorageMap/StorageVec/StorageString/StorageBytes相互嵌套、如何通过 storage namespace 注解为存储槽计算加入盐值以避免冲突以及如何绕过storage块直接调用底层read/write完成手动存储管理。读完本文你将能够在 Fuel 智能合约中自由组合各类存储集合、安全地处理跨合约加载时的存储槽冲突并掌握对未受storage块支持的数据类型如数组进行手工持久化的完整方案。适用前提本文示例均为 contract 类型程序因为只有合约被允许访问持久化存储StorageMapK, V等集合也仅能用于合约中这与 Storage Maps 文档中的说明一致。所有代码可在 examples/nested_storage_variables/src/main.sw、examples/storage_namespace/src/main.sw、examples/storage_example/src/main.sw 中查看完整实现。一、预备知识StorageKey 与存储槽模型在进入嵌套存储之前需要先理解 Sway 存储的底层模型。合约的持久化存储由 32 字节一个 word大小的存储槽storage slot组成每个存储变量通过编译期计算得到的b256槽键slot key定位。标准库用StorageKeyT封装了这一寻址信息从源码看其结构包含三个字段storage_key.swslot32 字节存储槽的键b256offset从slot起始的偏移量以 word 为单位field_id用于区分可能位于同一存储位置的多个零尺寸zero-sized存储条目的标识符。StorageMapK, V、StorageVecT、StorageBytes、StorageString本质上都是零尺寸的存储类型storage types它们本身不占用槽位全部行为实现在各自的方法中——这一点与普通VecT不同也与 Storage Maps 文档中StorageMapK, V本身是空结构体的描述相互印证。每个存储类型的槽位计算都经过精心设计以避免碰撞。以StorageMap为例其get_slot_key实现storage_map.sw为fn get_slot_key(self, key: K) - b256 { sha256((STORAGE_MAP_DOMAIN, key, self.field_id())) }即用sha256对「存储域前缀STORAGE_MAP_DOMAIN 用户键 字段 ID」做哈希。其中STORAGE_MAP_DOMAIN是一个单字节前缀源码注释明确指出这是为了确保映射中元素的槽位 pre-image 永远不会与编译器为存储字段生成的 pre-image 相同storage_map.sw。StorageKey::get(key)返回的正是以该哈希为槽键的StorageKeyVstorage_map.sw这意味着嵌套时只需把父类型算出的StorageKey继续当作子类型的槽位上下文即可——这就是嵌套存储能够成立的根本原因。StorageVecT的push/pop/get则通过read_quads::u64(self.field_id(), 0)读取/更新长度字段来维护动态数组语义storage_vec.swget(index)返回OptionStorageKeyV当index len时返回Nonestorage_vec.sw。二、Nested Storage Collections嵌套存储集合通过StorageKey你可以在一个存储集合中存放另一个存储集合例如把StorageString存进StorageMapK, V把StorageVecT存进StorageMapK, V或者把StorageBytes存进StorageVecT。2.1 嵌套存储声明与导入下面的storage块声明了三种常见的嵌套存储类型见 nested_storage_variables/src/main.swstorage { nested_map_vec: StorageMapu64, StorageVecu8 StorageMap {}, nested_map_string: StorageMapu64, StorageString StorageMap {}, nested_vec_bytes: StorageVecStorageBytes StorageVec {}, }使用前必须进行存储初始化为每个存储集合显式赋予空实例StorageMap {}、StorageVec {}。NOTE导入存储类型时请务必使用 glob 操作符例如use std::storage::storage_vec::*。嵌套示例中对应的完整导入为nested_storage_variables/src/main.swuse std::{ bytes::Bytes, hash::{Hash, sha256}, storage::{ storage_bytes::*, storage_string::*, storage_vec::*, }, string::String, };其中Hashtrait 需要显式导入——虽然StorageMapK, V已在标准库 prelude 中但其get/insert方法要求键类型实现Hash见 storage_map.sw 的where K: Hash约束以及 blockchain-development/storage.md 中的相关警告。2.2 在StorageMapK, V中存放StorageVecT写入见 nested_storage_variables/src/main.sw#[storage(write)] fn store_map_vec() { // Setup and initialize storage for the StorageVec. storage.nested_map_vec.try_insert(10, StorageVec {}); // Method 1: Push to the vec directly storage.nested_map_vec.get(10).push(1u8); storage.nested_map_vec.get(10).push(2u8); storage.nested_map_vec.get(10).push(3u8); // Method 2: First get the storage key and then push the values. let storage_key_vec: StorageKeyStorageVecu8 storage.nested_map_vec.get(10); storage_key_vec.push(4u8); storage_key_vec.push(5u8); storage_key_vec.push(6u8); }这里有两种等价写法方法 1直接访问storage.nested_map_vec.get(10).push(...)链式调用方法 2先取StorageKey再操作先let key storage.nested_map_vec.get(10)拿到类型为StorageKeyStorageVecu8的键再在键上调用push。注意第一步try_insert(10, StorageVec {})由于存储集合在编译期不会自动初始化若在写入前该键尚未初始化直接get访问会 revert详见 blockchain-development/storage.md 的说明。因此写入前必须用try_insert为键10建立空的StorageVec。读取见 nested_storage_variables/src/main.sw#[storage(read, write)] fn get_map_vec() { // Method 1: Access the StorageVec directly. let stored_val1: u8 storage.nested_map_vec.get(10).pop().unwrap(); let stored_val2: u8 storage.nested_map_vec.get(10).pop().unwrap(); let stored_val3: u8 storage.nested_map_vec.get(10).pop().unwrap(); // Method 2: First get the storage key and then access the value. let storage_key: StorageKeyStorageVecu8 storage.nested_map_vec.get(10); let stored_val4: u8 storage_key.pop().unwrap(); let stored_val5: u8 storage_key.pop().unwrap(); let stored_val6: u8 storage_key.pop().unwrap(); }pop()返回OptionV因此用unwrap()取出实际值当向量为空时pop返回None对应 storage_vec.sw 中len 0的短路逻辑。2.3 在StorageMapK, V中存放StorageStringStorageString与StorageVec不同它不支持按索引的逐元素读写只能将整个String一次性写入或读出。写入见 nested_storage_variables/src/main.sw#[storage(write)] fn store_map_string() { // Setup and initialize storage for the StorageString. storage.nested_map_string.try_insert(10, StorageString {}); // Method 1: Store the string directly. let my_string String::from_ascii_str(Fuel is blazingly fast); storage.nested_map_string.get(10).write_slice(my_string); // Method 2: First get the storage key and then write the value. let my_string String::from_ascii_str(Fuel is modular); let storage_key: StorageKeyStorageString storage.nested_map_string.get(10); storage_key.write_slice(my_string); }先通过String::from_ascii_str构造字符串再调用write_slice写入。与上一节同理try_insert(10, StorageString {})负责初始化该键的存储。读取见 nested_storage_variables/src/main.sw#[storage(read)] fn get_map_string() { // Method 1: Access the string directly. let stored_string: String storage.nested_map_string.get(10).read_slice().unwrap(); // Method 2: First get the storage key and then access the value. let storage_key: StorageKeyStorageString storage.nested_map_string.get(10); let stored_string: String storage_key.read_slice().unwrap(); }read_slice()返回OptionString配合unwrap()使用。底层的切片读写由标准库storable_slice.sw提供其中write_slice已标记为 deprecated推荐使用write_slice_quads按 quad 对齐写入或write_slice_slot按槽直接写入read_slice_quads/read_slice_slot同理storable_slice.sw、storable_slice.sw。2.4 在StorageVecT中存放StorageBytes第三种嵌套形态是把StorageBytes放进StorageVecT。注意StorageBytes与StorageVec的语义差异StorageBytes将字节紧凑打包存储更省 gas但只能整体读写无法像StorageVecT那样单独 push/pop 元素若需要频繁修改推荐改用StorageVecu8此建议同样适用于顶层存储见 blockchain-development/storage.md。写入见 nested_storage_variables/src/main.sw#[storage(write)] fn store_vec() { // Setup Bytes to store let mut my_bytes Bytes::new(); my_bytes.push(1u8); my_bytes.push(2u8); my_bytes.push(3u8); // Setup and initialize storage for the StorageBytes. storage.nested_vec_bytes.push(StorageBytes {}); // Method 1: Store the bytes by accessing StorageBytes directly. storage .nested_vec_bytes .get(0) .unwrap() .write_slice(my_bytes); // Method 2: First get the storage key and then write the bytes. let storage_key: StorageKeyStorageBytes storage.nested_vec_bytes.get(0).unwrap(); storage_key.write_slice(my_bytes); }这里storage.nested_vec_bytes.push(StorageBytes {})先向向量压入一个空的StorageBytes完成初始化由于StorageVecT::get返回OptionStorageKeyV需要unwrap()取出StorageKeyStorageBytes后再write_slice。读取见 nested_storage_variables/src/main.sw#[storage(read, write)] fn get_vec() { // Method 1: Access the stored bytes directly. let stored_bytes: Bytes storage.nested_vec_bytes.get(0).unwrap().read_slice().unwrap(); // Method 2: First get the storage key and then access the stored bytes. let storage_key: StorageKeyStorageBytes storage.nested_vec_bytes.get(0).unwrap(); let stored_bytes: Bytes storage_key.read_slice().unwrap(); }读取结果类型为Bytes即 Sway 标准库的堆上字节集合类型std::bytes::Bytes。三、Storage Namespace为存储槽计算加盐当合约代码被加载到与其他合约共享的环境时不同来源的存储变量可能计算出相同的槽位从而发生存储碰撞。如果你希望存储中的值被定位到不同的位置可以使用namespace 注解为槽位计算加入一个盐值salt从根源上规避冲突。语法是在storage块内部、变量名之前用一个命名字段包一层storage { example_namespace { foo: u64 0, }, }完整示例见 storage_namespace/src/main.sw。声明之后访问方式与普通存储变量完全一致——storage.foo.write(amount)写入、storage.foo.try_read().unwrap_or(0)读取abi StorageNamespaceExample { #[storage(write)] fn store_something(amount: u64); #[storage(read)] fn get_something() - u64; } impl StorageNamespaceExample for Contract { #[storage(write)] fn store_something(amount: u64) { storage.foo.write(amount); } #[storage(read)] fn get_something() - u64 { storage.foo.try_read().unwrap_or(0) } }推荐使用try_read()而非read()因为前者在槽位尚未写入时返回OptionT此处用unwrap_or(0)兜底能避免未初始化访问导致的 revert。namespace 的名字会参与该命名空间下所有变量的槽位派生计算因此不同的 namespace 会产出不同的槽键集合即使变量名相同也不会互相覆盖。这一机制与 blockchain-development/storage.md 中介绍的storage块声明模型一脉相承只是额外引入了命名空间维度。四、Manual Storage Management手动存储管理除了声明式storage块你还可以直接调用标准库提供的底层存储 API——std::storage::storage_api::write与std::storage::storage_api::read——来操作 FuelVM 的存储原语。采用这种方式时内部存储键必须由你手动指定编译器不会替你计算槽位。以下是一个完整的最小示例storage_example/src/main.swcontract; use std::storage::storage_api::{read, write}; abi StorageExample { #[storage(write)] fn store_something(amount: u64); #[storage(read)] fn get_something() - u64; } const STORAGE_KEY: b256 0x0000000000000000000000000000000000000000000000000000000000000000; impl StorageExample for Contract { #[storage(write)] fn store_something(amount: u64) { write(STORAGE_KEY, 0, amount); } #[storage(read)] fn get_something() - u64 { let value: Optionu64 read::u64(STORAGE_KEY, 0); value.unwrap_or(0) } }关键点拆解手动指定存储键这里定义了一个常量STORAGE_KEYb256全零所有读写都围绕该键进行。你可以为不同的数据分配不同的b256常量作为各自的槽键。write(slot, offset, value)将value写入从slot开始、偏移offset以 word 为单位的存储位置。从标准库源码storage_api.sw看值按 32 字节槽存放若跨槽边界则继续写入下一个槽offset可以超出slot边界如 offset 为 4 表示下一个槽的开头。若T是零尺寸类型则不会发生任何存储访问。写入前若目标槽已有部分数据会先读取被部分覆盖的旧数据再合并写回对应 1 次读取 1 次写入的访问成本。read::T(slot, offset)按类型T从指定槽与偏移处读取返回OptionT槽位为空时返回None因此示例中用unwrap_or(0)提供默认值。存储注解不可省略写入函数需要#[storage(write)]读取函数需要#[storage(read)]这与声明式存储的要求一致。Note虽然read/write可以用于任何数据类型但它们主要应被用于数组array——因为数组目前还不受storage块支持见 blockchain-development/storage.md 中受支持的集合列表StorageMapK, V、StorageVecT、StorageBytes、StorageString均不包含数组。此外所有数据类型都可以无限制地用作StorageMapK, V的键类型和/或值类型因此在需要以任意类型为键做映射存储时优先考虑StorageMap而非手动管理。补充说明底层槽位偏移计算的细节在storage_api.sw的slot_calculatorT(slot, offset)中实现storage_api.sw它会根据T的大小推算出实际首槽、占用槽数以及首槽内的起始 wordwrite/read内部正是基于该计算定位最终槽位的。五、小结三种高级存储技术的适用场景技术核心语法适用场景关键注意事项嵌套存储集合StorageMapK, StorageVecV、StorageMapK, StorageString、StorageVecStorageBytes等需要映射→集合集合→字节串等复合结构如按账户维度维护各自的动态数据写入前必须try_insert/push初始化导入存储类型需用 glob 操作符StorageString/StorageBytes只能整体读写Storage Namespacestorage { ns { field: T init } }加载外部合约代码、多合约共享环境时规避存储槽碰撞namespace 名为槽位计算提供盐值访问语法与普通存储一致手动存储管理write(slot, offset, value)/read::T(slot, offset)存储不受storage块支持的数组等类型完全掌控槽位布局必须自行定义并维护b256存储键read返回OptionT需处理空值函数需标注#[storage(read)]/#[storage(write)]三种技术都建立在相同的底层模型之上StorageKeyT承载的「槽 偏移 字段 ID」寻址、标准库storage模块storage.sw提供的storage_api/storage_key/storage_map/storage_vec/storage_bytes/storage_string等子模块以及 FuelVM 的__state_load_quad/__state_store_quad存储指令。想进一步了解基础用法可继续阅读 blockchain-development/storage.md 与 common-collections/storage_map.md。【免费下载链接】sway Empowering everyone to build reliable and efficient smart contracts.项目地址: https://gitcode.com/GitHub_Trending/sw/sway创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询