条件控制与循环控制:Move 语言if、match、while、for与loop完全指南(aptos-core 实战版)
【免费下载链接】aptos-coreAptos is a layer 1 blockchain built to support the widespread use of blockchain through better technology and user experience.项目地址: https://gitcode.com/GitHub_Trending/ap/aptos-core
本指南以 aptos-core 仓库内 Move 官方语言参考文档 third_party/move/documentation/book/src/conditionals-and-loops.md 为骨架,系统讲解 Move 的条件表达式(
if)、多路分支match、以及三种循环构造(while、for、loop)的完整语义、类型规则、语法文法与版本演进。读完本文,你将掌握如何在 Move 模块与脚本中写出类型安全的分支与循环逻辑,理解break/continue的灵活类型、match的穷尽性分析与区间模式,并了解语言版本 2.1(循环标签)、2.4(match扩展)与 2.5(for上界作用域)对控制流的直接影响。
一、条件表达式if:表达式而非语句
在 Move 中,if是表达式(expression),而不是传统意义上的语句。它指明:仅当某个条件为真时,才求值某段代码。最基础的用法如下:
script { fun example() { if (x > 5) x = x - 5 } }这里有一个关键约束:条件必须是类型为bool的表达式。Move 不像某些语言那样对整型或任意对象做真值判断,条件必须是布尔值。
1.1 可选的else分支
if表达式可以带一个可选的else子句,指定当条件为假时要求值的另一个表达式:
script { fun example() { if (y <= 10) y = y + 1 else y = 10 } }无论条件真假,两个分支中只会有一个被求值,绝不会两个都求值。每个分支既可以是一个简单表达式,也可以是一个表达式块(expression block)。
1.2if作为有值表达式
分支表达式可以产生值,因此整个if表达式是有结果的:
script { fun example() { let z = if (x < 100) x else 100; } }当if用于产生值时,两个分支的类型必须兼容。所谓兼容,在多数场景下即类型一致:
script { fun example() { // x 和 y 必须是 u64 整数 let maximum: u64 = if (x > y) x else y; // 错误!两个分支类型不同(10u8 与 100u64) let z = if (maximum < 10) 10u8 else 100u64; // 错误!分支类型不同,因为缺省 false 分支是 () 而非 u64 if (maximum >= 10) maximum; } }最后一个错误很典型:当没有else子句时,false 分支缺省为单位值(unit)(),所以if (maximum >= 10) maximum的类型是u64 | (),不兼容,编译期直接报错。
1.3 缺省 else 分支等价于()
若不指定else子句,false 分支缺省为单元值。以下两种写法完全等价:
script { fun example() { if (condition) true_branch // 隐含的缺省:else () if (condition) true_branch else () } }1.4 与表达式块配合的惯用法
实践中,if表达式常与表达式块配合,实现多级分支与复合赋值逻辑。注意else if链的写法:
script { fun example() { let maximum = if (x > y) x else y; if (maximum < 10) { x = x + 10; y = y + 10; } else if (x >= 10 && y >= 10) { x = x - 10; y = y - 10; } } }1.5 条件表达式的文法
if表达式的文法规则如下:
if-expression→if (expression)expressionelse-clauseopt
else-clause→elseexpression
从文法可见,else子句后可接任意表达式,这也是else if链能够级联的原因。
二、match表达式:多路条件分支
match表达式是多路条件(multi-way conditional)。它先对括号中的判别式(discriminator)求值,然后按文本顺序依次尝试每个分支(arm):第一个模式匹配且可选的if守卫(guard)成立的分支被选中。每个分支的形态为pattern => body(可在模式和=>之间加可选的if guard):
/// 将 HTTP 状态码映射为人类可读的消息。 fun status_message(code: u16): vector<u8> { match (code) { 200 => b"OK", 404 => b"Not Found", 500 => b"Server Error", _ => b"Unknown", } }match最早在Move 2.0中针对枚举值引入,枚举专属的模式(变体模式、字段解构等)见 structs-and-enums.md 的 Matching Enum Values 一节。Move 2.4将match扩展到原始值、区间模式和原始值引用。更多结构体/枚举专属扩展(变体模式内嵌套字面量与区间模式、结构体/枚举引用判别式、混合元组判别式)见 structs-and-enums.md 的 Match Expression Extensions 一节。语言版本的完整说明见 move-versions.md。
2.1 匹配原始值(自语言版本 2.4)
match表达式的判别式可以是以下类型:bool、任意有符号或无符号整数、以及vector<u8>(字节串)。对应分支的模式使用惯常的字面量语法书写:
fun describe_command(s: vector<u8>): u64 { match (s) { b"start" => 1, b"stop" => 2, x"00" => 3, _ => 0, } }原始值元组同样受支持,各位置可以自由混用字面量、变量绑定或_通配符:
fun combine(p: u8, q: u8): u8 { match ((p, q)) { (0, 0) => 0, (0, 1) => 1, (1, 0) => 2, (1, 1) => 3, _ => 255, } }注意:address不支持作为原始值判别式。
2.2 区间模式(Range Patterns,自语言版本 2.4)
区间模式在单个分支中匹配一段连续的整数值区间。两个边界都是整数字面量(有符号整数字面量可携带一元负号,如-5..5)。支持的形态如下:
| 模式 | 匹配条件 |
|---|---|
lo..hi | lo <= x && x < hi |
lo..=hi | lo <= x && x <= hi |
..hi | x < hi |
..=hi | x <= hi |
lo.. | lo <= x |
几点关键语义:
- 裸的
..不是区间模式;在结构体或变体模式内部,它保持原有的部分字段通配(partial-field wildcard)语义,见 structs-and-enums.md 的 Partial Patterns 一节。 - 穷尽性分析中,字面量模式被视为单点区间(singleton range),因此相邻的字面量与区间可以共同覆盖整数类型的整个值域,无需
_兜底分支:
fun classify(x: u8): u64 { match (x) { 0 => 0, 1..=127 => 1, 128..=255 => 2, } }- 区间模式适用于所有整数类型,包括跨零的有符号类型:
fun sign(x: i32): u8 { match (x) { ..0 => 0, // 负数 0 => 1, // 零 1.. => 2, // 正数 } }- 空区间或反转区间在编译期被直接拒绝:
// 全部报错:空区间模式。 match (x) { 5..5 => 1, _ => 0 } match (x) { 10..5 => 1, _ => 0 } match (x) { 10..=5 => 1, _ => 0 }- 区间模式也可嵌套在结构体或枚举变体模式内部使用,见 Match Expression Extensions。
2.3 透过引用匹配(自语言版本 2.4)
match表达式的判别式可以是对原始值的不可变引用或可变引用。分支模式与值形态使用相同的字面量和区间语法:
fun bool_ref(b: &bool): u64 { match (b) { true => 1, false => 0 } } fun bytes_ref(bs: &vector<u8>): u64 { match (bs) { b"hi" => 1, _ => 0 } } fun int_ref(x: &u64): u64 { match (x) { 0..10 => 1, _ => 0 } }&mut判别式以同样方式支持。对结构体和枚举值的引用同样可以作为判别式,详见 Match Expression Extensions。
2.4 字面量与区间模式的使用边界
字面量和区间模式只在match分支内合法;它们在let绑定、赋值和 lambda 参数中被拒绝,因为这些位置是不可反驳的(irrefutable),必须匹配其类型的每一个值:
// 全部报错:此处不允许字面量和区间模式。 let S { x: 1 } = S { x: 1 }; let 1u64 = 1; let f = |1u64| 0;2.5 编译器中的match实现
从编译器实现看,Move 的match在编译前端以ExpData::Match(id, exp, arms)表示,后端字节码生成器在 third_party/move/move-compiler-v2/src/bytecode_generator.rs 中通过gen_match生成代码,逐分支匹配模式并可能分支到退出路径(exit path);不可反驳模式(如let解构)则走gen_match_from_temp的 Irrefutable 匹配路径。这印证了文档描述的分支按文本顺序尝试、逐 arm 匹配的实现机制。
三、循环构造:while、for与loop
Move 提供三种循环构造:while、for和loop。
3.1while循环
while构造重复执行循环体(类型为 unit 的表达式),直到条件(类型为bool的表达式)求值为false。
下面是一个简单while循环,计算从1到n的和:
script { fun sum(n: u64): u64 { let sum = 0; let i = 1; while (i <= n) { sum = sum + i; i = i + 1 }; sum } }无限循环是被允许的:
script { fun foo() { while (true) { } } }break:提前退出循环
break表达式可以在条件求值为false之前退出循环。下面的循环用break找出n大于 1 的最小因子:
script { fun smallest_factor(n: u64): u64 { // 假设输入不是 0 或 1 let i = 2; while (i <= n) { if (n % i == 0) break; i = i + 1 }; i } }break表达式不能在循环外使用。
continue:跳过本轮剩余部分
continue表达式跳过循环体剩余部分,直接进入下一轮迭代。下面的循环计算1, 2, ..., n的和,但跳过能被 10 整除的数:
script { fun sum_intermediate(n: u64): u64 { let sum = 0; let i = 0; while (i < n) { i = i + 1; if (i % 10 == 0) continue; sum = sum + i; }; sum } }continue表达式也不能在循环外使用。
break与continue的类型
break和continue与return、abort类似,可以拥有任意类型。这一灵活类型在需要让分支产生值的场景下很有用。第一个例子:break在if-else链中充当类型为u64的值:
script { fun pop_smallest_while_not_equal( v1: vector<u64>, v2: vector<u64>, ): vector<u64> { let result = vector::empty(); while (!vector::is_empty(&v1) && !vector::is_empty(&v2)) { let u1 = *vector::borrow(&v1, vector::length(&v1) - 1); let u2 = *vector::borrow(&v2, vector::length(&v2) - 1); let popped = if (u1 < u2) vector::pop_back(&mut v1) else if (u2 < u1) vector::pop_back(&mut v2) else break; // 这里 `break` 的类型是 `u64` vector::push_back(&mut result, popped); }; result } }第二个例子:continue在if-else链中充当类型为&vector<address>的值:
script { fun pick( indexes: vector<u64>, v1: &vector<address>, v2: &vector<address> ): vector<address> { let len1 = vector::length(v1); let len2 = vector::length(v2); let result = vector::empty(); while (!vector::is_empty(&indexes)) { let index = vector::pop_back(&mut indexes); let chosen_vector = if (index < len1) v1 else if (index < len2) v2 else continue; // 这里 `continue` 的类型是 `&vector<address>` vector::push_back(&mut result, *vector::borrow(chosen_vector, index)) }; result } }3.2for表达式:整数区间迭代
for表达式在一个由整数类型的lower_bound(含)和upper_bound(不含)定义的区间上迭代,对区间内每个元素执行循环体。for面向的是迭代次数由特定区间决定的场景。
计算0到n-1区间元素和的例子:
script { fun sum(n: u64): u64 { let sum = 0; for (i in 0..n) { sum = sum + i; }; sum } }关键语义如下:
- 循环迭代变量(上例中的
i)必须是数值类型,由边界推断得出; - 边界(这里的
0和n)可以是任意数值表达式;每个边界在循环开始前按源码顺序恰好求值一次; - 下界总是在迭代变量引入之前求值,因此下界永远不能引用迭代变量;
- 迭代变量被设为下界(这里的
0),每轮迭代后自增;一旦达到或超过上界(这里的n),循环退出; - 当循环体不需要索引时,可用
_作为迭代变量名:for (_ in 0..n) { ... }。
版本相关行为:上界求值的作用域取决于语言版本。自语言版本 2.5起,上界在迭代变量的作用域之外求值,因此对上界中提及的迭代变量名,将引用外层绑定——给定
let i = 5; for (i in 0..i) { ... },上界i读取外层的5,循环执行 5 次。在语言版本 2.5 之前,上界在迭代变量处于作用域内时求值,因而读取的是迭代变量(绑定到下界),同样的循环执行 0 次。只要上界引用了被迭代变量遮蔽的名字,编译器就会发出警告,因为跨越 2.5 边界重新编译会改变循环行为。
for循环中的break与continue
与while循环类似,break可提前退出for循环,continue可跳过当前迭代进入下一轮。下面的例子同时演示两者:循环遍历0到n-1并求和,用continue跳过能被 3 整除的数,用break在遇到大于 10 的数时停止:
script { fun sum_conditional(n: u64): u64 { let sum = 0; for (iter in 0..n) { if (iter > 10) { break; // 若数字大于 10 则退出循环 }; if (iter % 3 == 0) { continue; // 若数字能被 3 整除则跳过当前迭代 }; sum = sum + iter; }; sum } }3.3loop表达式:无限循环
loop表达式重复执行循环体(类型为()的表达式),直到命中break。没有break的loop将永远循环下去:
script { fun foo() { let i = 0; loop { i = i + 1 } } }用loop重写sum函数:
script { fun sum(n: u64): u64 { let sum = 0; let i = 0; loop { i = i + 1; if (i > n) break; sum = sum + i }; sum } }continue同样可以用于loop内部。下面是前面用while实现的sum_intermediate,改用loop重写:
script { fun sum_intermediate(n: u64): u64 { let sum = 0; let i = 0; loop { i = i + 1; if (i % 10 == 0) continue; if (i > n) break; sum = sum + i }; sum } }3.4 三种循环表达式的类型
Move 的循环都是有类型的表达式:
while和for表达式的类型恒为():
script { fun example() { let () = while (i < 10) { i = i + 1 }; let () = for (i in 0..10) {}; } }- 若
loop包含break,其表达式类型为单元():
script { fun example() { (loop { if (i < 10) i = i + 1 else break }: ()); let () = loop { if (i < 10) i = i + 1 else break }; } }- 若
loop没有break或continue,则与return、abort、break、continue一样,可以拥有任意类型:
script { fun example() { (loop (): u64); (loop (): address); (loop (): &vector<vector<u8>>); } }这一设计逻辑自洽:没有退出点的loop永远不会正常返回,因此它可以被强制为任何类型而不会产生不健全的类型推断。从编译器实现看,loop在 bytecode_generator.rs 中以ExpData::Loop表示,并通过LoopContext栈跟踪嵌套循环,LoopCont(即break/continue)通过嵌套深度(nest)定位目标循环,这与循环标签的实现机制直接相关。
3.5 循环标签:跳出外层循环(自语言版本 2.1)
自语言版本 2.1起,while或loop语句可以带一个标签(label),供break或continue语句引用。在存在嵌套循环时,这允许引用外层循环:
script { fun example(x: u64): u64 { 'label1: while (x > 10) { loop { if (x % 2 == 0) { x -= 1; continue 'label1; } else if (x < 10) { break 'label1 } else x -= 2 } }; x } }在上例中,内层loop的continue 'label1直接让控制流回到外层while的下一轮迭代,break 'label1则直接退出外层while。循环标签是 Move 2.1 引入的核心特性之一,与复合赋值(x += n)、下划线参数通配等一同构成该版本的语言能力(见 move-versions.md)。
四、实战要点小结
| 构造 | 用途 | 类型 | 可配 break/continue | 版本引入 |
|---|---|---|---|---|
if (cond) a else b | 两路条件分支,可产生值 | 两分支的兼容类型(无 else 时默认()) | — | 语言最初 |
match (d) { pat => body, ... } | 多路条件,含守卫与穷尽性检查 | 所有分支的兼容类型 | — | 枚举:2.0;原始值/区间/引用:2.4 |
while (cond) { ... } | 条件循环 | () | 支持 | 语言最初 |
for (i in lo..hi) { ... } | 整数区间迭代(下界含、上界不含) | () | 支持 | 语言最初(上界作用域:2.5) |
loop { ... } | 无限循环,靠break退出 | 有 break 为();无 break/continue 可为任意类型 | 支持 | 语言最初(标签:2.1) |
- 条件必须是
bool:if、while的条件表达式类型必须为bool; - 分支类型必须兼容:
if/match用作值时,各分支类型必须兼容;缺省 else 分支是(); match的穷尽性:字面量视为单点区间,相邻字面量与区间可完整覆盖整数值域而无须_分支;空区间(如5..5、10..=5)在编译期报错;break/continue是多态的:可在需要特定值类型的位置使用;for上界求值位置随版本变化:2.5 起在迭代变量作用域之外求值,跨版本重编译语义不同,编译器会警告被遮蔽名字的引用;- 标签只在
while与loop上可用:break 'label/continue 'label可从内层循环控制外层循环。
延伸阅读
- 枚举变体模式与匹配: structs-and-enums.md
- 语言版本演进总览: move-versions.md
- 变量与复合赋值: variables.md
- 原始类型与整数语义: primitive-types.md
abort与assert!(与break/continue同类的多态控制表达式): abort-and-assert.md- 编译器字节码生成中的循环与匹配实现: bytecode_generator.rs
【免费下载链接】aptos-coreAptos is a layer 1 blockchain built to support the widespread use of blockchain through better technology and user experience.项目地址: https://gitcode.com/GitHub_Trending/ap/aptos-core
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考