Sway 变量体系详解:let 绑定、const 常量与变量遮蔽(Shadowing)
【免费下载链接】sway🌴 Empowering everyone to build reliable and efficient smart contracts.项目地址: https://gitcode.com/GitHub_Trending/sw/sway
本篇技术指南以 Sway 语言文档 docs/reference/src/documentation/language/variables/index.md 为核心,系统讲解 Sway 中变量的两类状态(不可变与可变)、let关键字在运行期的绑定语义、const常量在编译期的求值机制,以及重赋值与变量遮蔽(shadowing)的作用域技巧。读完本文,你将掌握在 Fuel 智能合约中正确声明、修改和复用变量名的全部实战方案,并理解其背后的编译器实现约束。
变量:命名引用与两种状态
在 Sway(Fuel 智能合约语言)中,变量(variable)是通过特定名称引用某份信息的方式,其承载的信息可以是多种数据结构中的任意一种,例如数值、布尔、字符串、元组、数组、结构体、枚举等。核心文档 index.md 开篇即点明:变量是"用名字引用信息"的抽象,而这份信息的形态完全由数据类型决定。
Sway 中的变量只有两种状态:
- 不可变(Immutable):声明之后可以被读取,但值不能被修改。
- 可变(Mutable):可以被读取,并且当且仅当新值与旧值类型相同时,其值可以被修改。
默认情况下,Sway 中所有变量都是不可变的,除非通过mut关键字显式声明为可变。这是 Sway 鼓励安全编程的重要方式之一,许多现代语言(如 Rust)也采用同样的默认约定——默认不可变意味着编译器可以更早地发现"误改数据"这类错误,从而降低合约出现逻辑漏洞的风险。
在随后的章节中,我们将逐一深入两个用于实例化信息的关键字(let与const),以及一种在不影响原始实例的前提下临时复用变量名的手段——变量遮蔽(variable shadowing)。
let:运行期绑定变量
let关键字用于在运行期为变量赋值。它只能出现在函数内部,并且当声明为可变时其值可以被改变。这一点与下面的const形成鲜明对比:let的值在程序执行到该语句时才真正被求值和绑定。
不可变let绑定
使用let声明一个值不能被改变的变量:
let foo = 5;上面这段代码摘自仓库中的可运行示例 docs/reference/src/code/language/variables/src/lib.sw。默认情况下foo是一个不可变的u64类型变量,值为5。这意味着我们可以把foo传递到各处进行读取,但它的值无法从5改为其他任何数值。
值得留意的是,Sway 会像大多数静态类型语言一样进行类型推断:let foo = 5;中整数字面量5默认推断为u64(在 Sway 中整数默认类型即u64,可参考 built-ins/numeric.md 中的数值类型章节)。
可变let绑定
通过mut关键字可以声明值可被修改的变量:
let mut foo = 5; foo = 6;同样摘自 lib.sw。foo先被初始化为5,随后被重新赋值为6。注意:赋值时新值的类型必须与声明时的类型相同,这是前文"可变"定义中"当且仅当新值与旧值类型相同"的落地点——Sway 不会在赋值时做隐式的类型转换。
赋值求值顺序的陷阱
Sway 文档特别提醒了一个容易被忽视的语义:当给可变变量赋值时,赋值语句的右侧(右值)会先于左侧(左值)被求值。看下面的示例(同样出自 lib.sw):
let mut array = [0, 0, 0]; let mut i = 0; array[i] = { i += 1; i };这里把块表达式{ i += 1; i }作为右值赋给array[i]。由于右值先求值,i会先从0增加到1,块表达式的值为1;随后左值array[i]中的i才被读取,此时已经是1。因此最终结果是array[1] = 1,数组变为[0, 1, 0],而非[1, 0, 0]。在合约代码中,如果左值下标依赖同一个可变变量,这种求值顺序会直接决定写入位置,务必小心。这也与 Sway 的求值模型保持一致:表达式按"右先左后"的顺序处理,避免在读取下标前右侧副作用已经改变了下标值而导致的歧义。
const:编译期常量
常量与不可变的let变量有相似之处,但存在三点关键差异(见 const.md):
- 常量总是在编译期求值,不会在运行期计算;
- 常量既可以在函数内部声明,也可以在全局作用域 /
impl作用域声明(let只能出现在函数内); const不能与mut关键字连用,常量天然不可变。
常量声明
定义常量时,使用const关键字,后跟名称和值的赋值:
const FOO = 5;该示例出自 lib.sw。上面的例子直接硬编码了值5,但常量声明同样可以使用函数调用,并且可以配合各种内置类型使用,例如:
const U64_MAX = u64::max(); const SIXTY_FOUR_BYTES: b256 = 0x0000000000000000000000000000000000000000000000000000000000000000;由于常量在编译期求值,其值必须能在编译阶段确定——因此运行时才可知的值(例如区块高度、调用者地址等动态数据)不能用于初始化常量。
impl self中的关联常量
常量还可以声明在impl块内部,此时它被称为关联常量(associated constant)。文档 const.md 给出了一个结构体上的示例:
struct Point { x: u64, y: u64, } impl Point { const ZERO: Point = Point { x: 0, y: 0 }; } fn main() -> u64 { Point::ZERO.x }这里ZERO是Point类型的关联常量,通过Point::ZERO的路径语法访问,字段x的值为0。关联常量把"与类型强相关的固定值"收纳到类型命名空间内,避免在全局作用域散落大量常量,是组织合约常量的良好实践。需要注意的是,在impl块内声明常量时,impl self(即对自身类型的实现)与显式命名的类型实现(如上面的impl Point)均可携带关联常量。
常量的编译期求值与文档目录中的配套资料
关于内置常量的完整清单,可进一步阅读 built-ins/constants.md;而const关键字在词法层面的定义可参见 sway-ast/src/keywords.rs,其中const与let、mut等同属保留关键字,由解析器在语法分析阶段识别。从实现层面看,常量在类型检查阶段会被当作编译期可折叠的项处理,这也是其"编译期求值"特性的底层来源。
重赋值与变量遮蔽(Shadowing)
前面我们看到,let变量的值可以通过mut修改。Sway 还提供了更进一步的两种手段:重赋值(reassignment)与变量遮蔽(variable shadowing)。需要特别说明的是:遮蔽只适用于let变量,常量(const)不能被遮蔽(见 variable-shadowing.md)。这一点在编译器的符号收集逻辑中也有体现:sway-core 在收集常量符号时区分了ConstShadowingMode(如Sequential与ItemStyle,参见 sway-core/src/semantic_analysis/ast_node/declaration/declaration.rs),常量遮蔽受到更严格的模式约束,普通变量则不受此限制。
重赋值:重新定义类型与值
我们可以通过在第一次声明之后再次实例化同名变量,来重新定义该变量的类型和值(见 lib.sw):
// Set `foo` to take the value of `5` and the default `u64` type let foo = 5; // Reassign `foo` to be a `str` with the value of `Fuel` let foo = "Fuel";第一次声明后foo是值为5的u64;第二次let foo = "Fuel"则在同一作用域创建了一个新的绑定,类型变为str(Sway 中的字符串字面量类型),值变为"Fuel"。注意这与mut修改有本质区别:mut修改要求新旧值类型相同,而重赋值可以改变类型——因为它本质上是"创建了一个同名新变量",而非修改旧变量的内存。从编译器视角看,第二次let引入的是一个全新的符号绑定,旧绑定在新的绑定点之后不再可访问。
变量遮蔽:用块作用域临时复用变量名
如果我们不想改变原始变量,只是想在某个局部范围内临时复用这个名字,可以使用块作用域{ ... }来约束新变量(见 lib.sw):
let foo = 5; { let foo = 42; } assert(foo == 5);foo(值为42)被定义在花括号{ }内部,只在{ ... }这个作用域内存在;离开块后,原始的foo(值为5)仍然保持原值,因此assert(foo == 5)通过。这正是"遮蔽"(shadowing)的含义:内层同名变量在块作用域内"遮住"了外层变量,但不修改外层绑定的值。
遮蔽在实际合约开发中非常实用,例如在函数内先用一个通用名字接收参数,在块内复用该名字进行临时计算,计算结束后外层变量不受影响;又如用if/match的块分支来约束临时状态,避免在函数体内累积大量mut变量。Sway 的类型检查器(sway-core/src/semantic_analysis/ast_node/modes.rs 及其周围的符号收集逻辑)会按作用域嵌套处理同名符号,内层作用域的绑定优先可见,从而支持这一语义。
实战小结与注意事项
综合上述文档与源码,编写 Sway 合约时关于变量的核心要点可总结如下:
| 特性 | let变量(不可变) | let mut变量(可变) | const常量 |
|---|---|---|---|
| 求值时机 | 运行期 | 运行期 | 编译期 |
| 声明位置 | 仅函数内部 | 仅函数内部 | 函数内部、全局 /impl作用域 |
| 是否可改值 | 否 | 是(要求类型相同) | 否(不可用mut) |
| 是否可重定义类型 | 可通过重赋值(新let) | 可通过重赋值(新let) | 否 |
| 是否可被遮蔽 | 是 | 是 | 否 |
除此之外,还有三个易错点值得在实战中反复检查:
- 赋值求值顺序:
array[i] = { i += 1; i }这类写法中右侧先求值,写入位置是自增后的下标,结果可能与你直觉相反。 - 类型一致性:对
mut变量赋值时,编译器要求新旧类型完全一致,不存在隐式转换;需要换类型请用重赋值。 - 作用域纪律:遮蔽只是"临时借用"名字,不会修改原始变量;在块外断言原始值仍可验证这一点,这也是文档示例使用
assert(foo == 5)的原因。
如何自己动手验证
文档中的全部示例都被组织成一个可运行的 Sway 库项目 docs/reference/src/code/language/variables/Forc.toml,入口文件为src/lib.sw,并通过std = { path = "../../../../../../sway-lib-std" }依赖标准库。你可以使用 Sway 的包管理器forc在本地对该目录执行forc build验证代码可编译,或结合forc test在测试函数中运行上述断言来观察遮蔽行为。更完整的语言级变量讨论,还可延伸阅读内置数据类型与函数相关章节,它们共同构成了 Sway 表达式的基石。
【免费下载链接】sway🌴 Empowering everyone to build reliable and efficient smart contracts.项目地址: https://gitcode.com/GitHub_Trending/sw/sway
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考