条件控制与循环控制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.4match扩展与 2.5for上界作用域对控制流的直接影响。一、条件表达式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-clauseoptelse-clause→elseexpression从文法可见else子句后可接任意表达式这也是else if链能够级联的原因。二、match表达式多路条件分支match表达式是多路条件multi-way conditional。它先对括号中的判别式discriminator求值然后按文本顺序依次尝试每个分支arm第一个模式匹配且可选的if守卫guard成立的分支被选中。每个分支的形态为pattern body可在模式和之间加可选的if guard/// 将 HTTP 状态码映射为人类可读的消息。 fun status_message(code: u16): vectoru8 { match (code) { 200 bOK, 404 bNot Found, 500 bServer Error, _ bUnknown, } }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.4match表达式的判别式可以是以下类型bool、任意有符号或无符号整数、以及vectoru8字节串。对应分支的模式使用惯常的字面量语法书写fun describe_command(s: vectoru8): u64 { match (s) { bstart 1, bstop 2, x00 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..hilo x x hilo..hilo x x hi..hix hi..hix hilo..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.4match表达式的判别式可以是对原始值的不可变引用或可变引用。分支模式与值形态使用相同的字面量和区间语法fun bool_ref(b: bool): u64 { match (b) { true 1, false 0 } } fun bytes_ref(bs: vectoru8): u64 { match (bs) { bhi 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与loopMove 提供三种循环构造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: vectoru64, v2: vectoru64, ): vectoru64 { 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链中充当类型为vectoraddress的值script { fun pick( indexes: vectoru64, v1: vectoraddress, v2: vectoraddress ): vectoraddress { 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 的类型是 vectoraddress 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 (): vectorvectoru8); } }这一设计逻辑自洽没有退出点的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.4while (cond) { ... }条件循环()支持语言最初for (i in lo..hi) { ... }整数区间迭代下界含、上界不含()支持语言最初上界作用域2.5loop { ... }无限循环靠break退出有 break 为()无 break/continue 可为任意类型支持语言最初标签2.1条件必须是boolif、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.mdabort与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),仅供参考