Sway 属性(Attributes)完全指南:元数据驱动智能合约的开发、测试与优化
【免费下载链接】sway🌴 Empowering everyone to build reliable and efficient smart contracts.项目地址: https://gitcode.com/GitHub_Trending/sw/sway
Sway 语言通过属性(Attributes)机制为代码元素附加元数据,从而开启测试、存储纯度声明、合约可支付性、内联优化提示、弃用警告等编译期能力。本指南基于仓库 sway 中的官方文档 语言参考 · 属性 编写,并结合源码与可运行示例(docs/reference/src/code/language/annotations/src/main.sw),系统讲解七类内置属性:#[storage(...)]、#[payable]、#[test(...)]、#[allow(...)]、#[inline(...)]、#[deprecated(...)],以及它们在合约、测试与优化场景中的完整用法。
从元数据到行为:Sway 属性是什么
属性(attribute)是一种元数据(metadatum),它附着在合约、函数、结构体、ABI 方法等代码元素之上,向编译器传达额外的语义信息。根据 语言参考,属性提供了超出普通类型与函数声明之外的附加功能。
在 sway-ast 中,属性声明的语法被定义为:
#[attribute] #[attribute_1, attribute_2] #[attribute()] #[attribute(arg)] #[attribute(arg_1, arg_2)] #[attribute(arg_1 = "value", arg_2 = true)]其中方括号内可以包含一个或多个属性,每个属性可以携带零个或多个参数,参数可以拥有键值对形式的赋值(sway-ast/src/attribute.rs)。文档注释(//!与///)在解析层面也被建模为属性声明,因此可以把注释视为属性的一个子集。
可复现的完整示例
为了让后续每个属性的讲解都能直接运行验证,先给出一个覆盖所有内置属性的完整合约示例(与 docs/reference/src/code/language/annotations/src/main.sw 一致):
contract; storage { my_storage_namespace { var: u64 = 0, } } abi MyContract { #[payable] fn deposit(); } #[storage(read)] fn read() { let variable = storage::my_storage_namespace.var.read(); } #[storage(write)] fn write() { storage::my_storage_namespace.var.write(storage::my_storage_namespace.var.read() + 1); } #[storage(read, write)] fn read_write() { let var = storage::my_storage_namespace.var.read(); storage::my_storage_namespace.var.write(var + 1); } #[allow(dead_code)] fn unused_function() {} #[test] fn equal() { assert_eq(1 + 1, 2); } #[test(should_revert)] fn unequal() { assert_eq(1 + 1, 3); } #[test(should_revert = "18446744073709486084")] fn assert_revert_code() { assert(1 + 1 == 3); } #[test(should_revert = "42")] fn custom_revert_code() { revert(42); } #[inline(never)] fn foo() {} #[inline(always)] fn bar() {} #[deprecated(note = "This is deprecated.")] struct DeprecatedStruct {} #[allow(deprecated)] fn using_deprecated_struct() { let _ = DeprecatedStruct {}; }下文将逐一拆解每个属性。所有代码均可放入一个contract文件中,通过forc build/forc test验证。
存储纯度:#[storage(read, write)]
#[storage(...)]属性声明一个函数的纯度(purity),即该函数是否:
- 读取存储(read)
- 写入存储(write)
- 既读取又写入存储(read, write)
- 既不读也不写——即纯函数(pure)
当函数是纯函数时,不需要添加任何存储属性,属性可以省略;只要函数涉及存储访问,就必须在函数签名上方放置正确的注解(attributes/storage.md)。
三种写法的语义如下:
| 写法 | 语义 | 典型场景 |
|---|---|---|
#[storage(read)] | 只读取存储,不修改 | 查询余额、读取配置 |
#[storage(write)] | 只写入存储,不读取 | 覆盖状态、初始化 |
#[storage(read, write)] | 读取并写入存储 | 读改写类逻辑,如计数器累加 |
示例:
#[storage(read)] fn read() { let variable = storage::my_storage_namespace.var.read(); } #[storage(write)] fn write() { storage::my_storage_namespace.var.write(storage::my_storage_namespace.var.read() + 1); } #[storage(read, write)] fn read_write() { let var = storage::my_storage_namespace.var.read(); storage::my_storage_namespace.var.write(var + 1); }存储声明使用命名空间(namespace)语法(my_storage_namespace),配合.read()/.write()方法完成状态读写。关于存储操作的完整说明,可参考文档 common storage operations(对应路径 docs/reference/src/documentation/operations/storage/index.md)。
接受资产:#[payable]
#[payable]属性用于允许一个 合约 的 函数 接受通过调用转发(forwarded)的资产(asset)。默认情况下,如果调用方向合约函数转发了资产,函数会拒绝该调用;只有显式标注了#[payable]的函数才允许接收资产(attributes/payable.md)。
典型写法是在 ABI 方法声明处标注:
abi MyContract { #[payable] fn deposit(); }该属性常与msg_amount()等上下文函数配合,用于实现充值、存款、购买等需要同时接收资产与更新状态的业务逻辑。需要注意的是,#[payable]只决定"能否接收资产",并不豁免其他安全检查(如重入防护、CEI 模式),这些仍需开发者自行保证。
单元测试:#[test]与#[test(should_revert)]
Sway 提供#[test]属性,用于在 Sway 代码中直接编写单元测试(attributes/test.md)。
成功用例
#[test]表示一个测试,只要测试执行过程中没有 revert(回滚),即判定为通过:
#[test] fn equal() { assert_eq(1 + 1, 2); }回滚用例
当测试的预期行为是代码应当回滚时,使用#[test(should_revert)]。此时如果测试确实发生了 revert,会被报告为通过:
#[test(should_revert)] fn unequal() { assert_eq(1 + 1, 3); }指定回滚码
should_revert还可以携带一个具体的回滚码(revert code),用于精确匹配测试中抛出的回滚值。示例中给出了两种典型用法(docs/reference/src/code/language/annotations/src/main.sw):
#[test(should_revert = "18446744073709486084")] fn assert_revert_code() { assert(1 + 1 == 3); } #[test(should_revert = "42")] fn custom_revert_code() { revert(42); }当assert失败时 Sway 会产生一个默认回滚码;而当开发者主动调用revert(42)时,则抛出自定义回滚码42。如果测试中实际产生的回滚码与should_revert指定的值一致,测试通过。例如custom_revert_code会精确匹配revert(42)的返回值。测试通过forc test命令执行。
内联优化:#[inline(never)]与#[inline(always)]
进行函数调用时,编译器既可能生成对函数定义处的调用指令,也可能将函数体代码复制(inline)到调用点,以减少额外代码生成(attributes/inline.md)。
Sway 编译器会基于内部启发式规则(internal heuristics)自动决定是否内联函数。#[inline(...)]属性用于建议(suggest)而非强制要求编译器采用某种生成策略:
#[inline(never)]:建议生成对函数定义处的调用代码(即不要内联)#[inline(always)]:建议将函数体复制到调用点(即尽量内联)
#[inline(never)] fn foo() {} #[inline(always)] fn bar() {}由于它只是"建议",编译器最终仍可能根据启发式规则做出不同决策;因此该属性适合作为性能调优的提示,而不是硬性保证。在循环热路径、递归函数、体积与速度权衡等场景中,开发者可以通过这两个关键字向编译器表达意图。
抑制警告:#[allow(...)]
#[allow(...)]属性用于关闭编译器对特定代码情况发出的警告(attributes/allow.md)。
#[allow(dead_code)]
关闭针对未使用(unused)代码的警告:
#[allow(dead_code)] fn unused_function() {}当项目处于开发初期、函数尚未被调用时,该属性可以避免编译器对未使用代码发出警告,同时保留函数定义。
#[allow(deprecated)]
关闭针对使用已弃用(deprecated)项时的警告:
#[allow(deprecated)] fn using_deprecated_struct() { let _ = DeprecatedStruct {}; }当代码中不可避免地需要引用一个已被标记弃用的项时(例如迁移期间仍需兼容旧 API),使用该属性可以显式声明"我知道这是弃用的",从而让编译输出保持干净。需要注意的是,#[allow(...)]只是静默警告,并不会改变被允许项的实际语义。
标记弃用:#[deprecated(note = "...")]
#[deprecated]属性将某个项标记为弃用,使得编译器在该项被使用的每个位置发出警告;该警告可以通过#[allow(deprecated)]关闭(attributes/deprecated.md)。
此外,可以通过note参数自定义警告消息:
#[deprecated(note = "This is deprecated.")] struct DeprecatedStruct {}这样,任何使用DeprecatedStruct的代码都会收到带有This is deprecated.提示的警告,帮助团队在升级过程中向调用方解释替代方案。弃用警告属于编译期提示,不影响编译产物。
属性如何进入编译器:AST 层解析
从实现角度看,属性在 sway-ast/src/attribute.rs 中被解析为AttributeDecl,它由AttributeHashKind(标识是外层#[...]还是内层#![...])和方括号包裹的、以逗号分隔的属性列表组成。每个属性(Attribute)可以携带括号包裹的参数列表(AttributeArg),参数可以是"仅名称"或"名称 = 值"两种形式。这套 AST 结构正好对应了上文各种写法的统一来源:
#[attribute(arg_1 = "value", arg_2 = true)]也就是说,无论#[storage(read, write)]、#[test(should_revert = "42")]还是#[deprecated(note = "...")],在语法层面都是同一种"属性 + 参数(可含键值对)"的通用结构,语义差异完全由属性名与参数内容决定。这解释了为什么 Sway 可以保持如此简洁的元数据语法。
总结:何时使用哪个属性
| 属性 | 作用对象 | 核心作用 |
|---|---|---|
#[storage(read)]/#[storage(write)]/#[storage(read, write)] | 函数 | 声明存储读写纯度,读取/写入存储时必需 |
#[payable] | 合约 ABI 函数 | 允许函数接受调用转发的资产 |
#[test] | 函数 | 声明单元测试(不 revert 即通过) |
#[test(should_revert)] | 函数 | 声明预期回滚的测试(revert 即通过) |
#[test(should_revert = "code")] | 函数 | 声明回滚测试并精确匹配回滚码 |
#[inline(never)]/#[inline(always)] | 函数 | 建议编译器不内联 / 内联(仅供参考) |
#[allow(dead_code)] | 任意项 | 关闭未使用代码警告 |
#[allow(deprecated)] | 任意项 | 关闭使用弃用项的警告 |
#[deprecated(note = "...")] | 任意项 | 标记弃用,使用处产生自定义提示警告 |
完整可运行的示例位于 docs/reference/src/code/language/annotations/src/main.sw,AST 级语法解析实现位于 sway-ast/src/attribute.rs。掌握这七类属性,即可在合约开发中精确控制存储访问声明、资产接收权限、单元测试策略、代码生成优化与 API 迁移流程。
【免费下载链接】sway🌴 Empowering everyone to build reliable and efficient smart contracts.项目地址: https://gitcode.com/GitHub_Trending/sw/sway
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考