Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Lambda 与代码块参数

本章节的 Lambda 指 Move 里一种匿名代码片段的写法:|参数 …| 表达式 或带类型的 |x: T| -> R { … }。它不是可以随便传来传去的「函数值」——在 Move 中,Lambda 只能作为 macro fun 调用的实参出现;编译器在宏展开时把它嵌进宏体,再按普通 Move 规则做类型检查与借用检查。


什么是 Lambda(在本章语境下)

若你熟悉 Rust、JavaScript 等语言里的闭包,可以把 Move 的 Lambda 想成:一段尚未单独命名、专门交给某个宏去「按需嵌入」的代码。它与普通 fun 的差别在于:

普通 funLambda
能否单独声明、到处传递可以(public fun 等)不能作为独立值类型传递
典型用途模块 API、可复用逻辑仅作宏参数,由宏展开成具体语句
是否有「函数指针」无(Move 无通用函数类型)无;展开后就是普通代码块

因此:Lambda = 宏专用的匿名代码参数,不是语言里与 u64vector<T> 并列的「Lambda 类型」。


Lambda 的类型形式(对宏可见)

宏定义里若需要「接收一段代码」,会把形参声明成 Lambda 类型,写法为:

|T1, T2, ...| -> R

含义是:接受若干个参数,类型依次为 T1T2…,整体这段代码的结果类型R

  • 省略返回类型时,默认为 ()(与「只产生副作用、不返回值」的块一致)。
  • 参数类型、返回类型都必须写清楚(或由宏实现推断上下文);与 §11.4 展开与求值 一起读,更易理解「宏为什么需要这种类型」。

示例(类型层面的直觉,不必死记名字):

|u64, u64| -> u128          // 两个 u64,结果为 u128
|&u64|                       // 一个不可变引用,结果为 ()
|&mut u64| -> ()            // 可变引用,常用于原地修改

标准库里 vector::fold! 的累加器一侧、do_ref! 的元素类型等,都会在展开后对应到这类签名。


Lambda 的定义形式

1. 省略参数类型(由上下文推断)

在能推断的前提下,可以只写参数名:

|x| x + 1
|a, b| a + b
|_| 0u64                    // 常用:占位「我不关心这个参数」

2. 显式标注参数类型与返回类型

需要时写成:

|x: u64| -> u64 { x + 1 }
|acc: u64, e: u64| -> u64 { acc + e }

花括号 { … } 表示块体:内部可以有多条语句,最后一式或显式构造的值为返回(与 R 一致)。

3. 单表达式体(无花括号)

|x| 2 * x

等价于「单表达式、返回类型可推断」的简写。

4. 与 & / &mut 配合

遍历时常出现引用形参,例如「对每个元素的引用做只读访问」:

vec.do_ref!(|e| use_read_only(*e));
vec.do_mut!(|e| *e = *e + 1);

这里 e 的类型由宏展开后的签名决定(如 &T&mut T),写错借用方式时,报错会落在展开后的代码上,见 §11.8


使用方式:只能作为宏实参

合法:宏调用里传入 Lambda:

let v = vector[1u64, 2, 3];
let s = v.fold!(0u64, |acc, x| acc + x);

option::some(42u64).do!(|n| assert!(n == 42));

不合法:把 Lambda 赋给变量、放进 struct 字段、当作普通函数参数传递——这些在 Move 中没有对应的类型与语义。

// 不能编译 — Move 没有「Lambda 类型」的值
// let f = |x| x + 1;

记住:|...| 只出现在 xxx!( ... ) 的括号里


捕获外围变量

Lambda 体内部可以使用当前作用域里已经存在的变量(常称为「捕获」)。展开后,这些名字就是普通 Move 代码里的变量,因此必须满足:

  • 所有权:不能捕获后违反移动/二次使用规则。
  • 借用:若 Lambda 展开为多次使用某引用,须符合借用检查器要求。

示例(用 fold! 在 Lambda 里使用外层变量 k):

let k = 10u64;
let v = vector[1u64, 2, 3];
let sum = v.fold!(0u64, |acc, x| acc + x * k);

若某次展开导致「同一值被移动两次」或「可变与不可变借用冲突」,错误信息与 §11.4 里讲的「片段代入」一致:本质是展开后的普通 Move 代码不合法。


实例

例 1:fold! 累加

let v = vector[1u64, 2, 3, 4, 5];
let sum = v.fold!(0u64, |acc, x| acc + x);
// sum == 15
  • 第一个 Lambda 参数:累加器 acc(类型与初值 0u64 一致)。
  • 第二个:当前元素 x

例 2:do_ref! 只读遍历

let mut total = 0u64;
let v = vector[10u64, 20, 30];
v.do_ref!(|e| total = total + *e);

例 3:tabulate! 用下标生成向量

let indices = vector::tabulate!(5, |i| i);
// vector[0,1,2,3,4]

例 4:option::do! 仅在 some 时执行

let opt = option::some(100u64);
opt.do!(|n| assert!(n > 0));

例 5:占位参数 |_|

重复执行某操作、但不需要元素或下标时:

3u8.do!(|_| do_something());

限制与常见误区

  1. 出现位置
    只能写在 name!(...) 的实参里。任何试图「保存 Lambda、返回 Lambda」的写法都不符合 Move 设计。

  2. 类型必须与宏期望一致
    宏内部对 Lambda 形参有固定签名(如 fold! 的两个参数)。实参 Lambda 的参数个数、类型、返回类型必须匹配,否则在展开后类型检查失败。

  3. 不是延迟到链上再「解释执行」
    Lambda 与宏一起在编译期展开,链上只有普通字节码,没有「Lambda 对象」。

  4. 捕获与多次求值
    若宏展开后同一表达式被复制多份(取决于具体宏实现),可能带来「重复移动」或「重复执行副作用」类问题;以具体宏文档与编译报错为准,必要时先展开成手写循环对照(见 §11.8)。

  5. fun 的取舍
    需要命名、复用、跨模块调用的逻辑,应写成普通 fun,在 Lambda 里调用该函数即可,而不是把业务全堆在 Lambda 里。


小结

要点内容
本质仅用于宏实参的匿名代码片段,不是独立函数类型
类型写法|T1, T2, …| -> R,默认 R()
定义可省略类型、可用 |_| 占位、可有 |x: T| -> R { … } 块体
捕获遵循展开后的所有权与借用规则
限制不能脱离宏单独存在;不能与「高阶函数值」类比过度

写完 Lambda 后务必 sui move build:类型与借用错误会以展开后的代码为基准报错,熟悉这一点后排错会更快。更多标准库宏与 Lambda 的搭配见 §11.6§11.7