30 秒看懂
在 OCaml Unboxed 关于实验性局部值的第二期中,讲者将局部标注从参数扩展到变量和返回值。编译器示例展示了逃逸错误、尾位置的特殊处理,以及 `exclave_` 和局部返回标注如何让问候列表在调用方区域中分配。
- 1局部变量不能逃逸将局部变量 `x` 赋给生命周期更长的字符串引用会产生逃逸错误。
- 2局部元组可以包含局部值当元组本身不逃逸时,编译器会推断包含局部值的元组也是局部的。
- 3尾位置的特殊规则同一个 `ignore` 调用放在返回位置时可能失败;讲者留待以后解释详细规则。
- 4辅助函数的类型需要标注局部性普通的 `string -> string list` 签名不保证实参保持局部。
- 5提前结束区域`exclave_` 让问候列表成为外层区域的一部分,返回值上的 `local_` 则传达调用约定和结果的局部性。
- 6参数与局部别名不同示例编译器允许在 exclave 中使用参数,但拒绝使用函数内部定义的局部别名。
要点建议
- 局部变量不能逃出其区域,包括通过生命周期更长的引用逃逸。
- 包含局部值的结构如果不逃逸,本身也可以是局部的。
- 返回位置或尾位置有特殊的局部性规则,讲者留待之后解释。
- `exclave_` 提前结束函数的区域,让后续表达式可以在外层区域分配。
- 局部返回标注表明函数可能在调用方区域分配,返回结果也不能逃出该区域。
- 在示例中,局部参数可以在 exclave 内使用,而局部绑定的别名不可以。
按章节阅读整理后的全文
局部变量与逃逸
讲者回顾局部参数,并在命令式函数中添加局部变量。将该变量存入引用会产生逃逸错误。
上次我们看到了局部参数:作为实参接收的值不会逃出其区域,因此函数不会将它存起来,也不会把它放进以后会存到别处的结构中。今天我想将这个概念推广到局部变量,并解释为什么可能需要局部返回值。
我从一个类型为 `unit -> unit` 的小型命令式函数开始,用 `let local_ x = "you"` 绑定 `x`。这个标注告诉我,即使函数包含复杂的命令式代码,它也不能把这个局部值存到会让它逃逸的地方。
例如,我在函数外创建一个字符串引用,并尝试将 `x` 赋给它。编译器报告该值逃出了其区域,这正是局部标注应当阻止的情况。去掉赋值后,程序又可以正常工作。

忽略局部值与尾位置
示例中的实验性编译器需要一个带局部类型的 `ignore`,并能推断局部元组。返回位置遵循特殊规则,之后会进一步解释。
在这里展示的实验性编译器中,即便使用内置的 `ignore x` 也会产生逃逸错误:内置函数还不了解局部值。我另外定义一个类型为 `local_ 'a -> unit` 的 `ignore`,程序便能工作。这一功能仍在开发中,因此还有一些不完善之处。
我再添加一个局部变量 `y`,并将 `(x, y)` 传给支持局部值的 `ignore`。两个值不会仅仅因为放进元组就逃逸:元组本身并不逃逸。编译器会推断包含局部值的结构本身也必须是局部的。


如果把 `ignore (x, y)` 直接放在函数的返回位置,就会出现问题:编译器报告逃逸。返回位置也称为尾位置,它有特殊的局部性规则。我会在之后的视频中解释这些规则;现在先把调用移出返回位置,然后返回 `()`。
重构问候函数
构建列表的辅助函数揭示了参数和返回值局部性的重要性。最初的函数签名允许或导致逃逸。
接下来,我为 `x` 和 `y` 构建局部问候列表,每个列表都包含 `"Hello,"` 和对应的值。代码可以工作,但重复了相同的操作。为了避免重复,我提取一个接收字符串并返回字符串列表的辅助函数 `greet`。
用局部变量 `x` 调用辅助函数会失败。它的普通类型 `string -> string list` 没有承诺实参不会被存到某处或以其他方式逃逸。将参数标为 `local_` 就作出了这个承诺,但编译器随后报告另一个错误:函数实现把局部参数放进了返回的列表。


我希望这个列表属于外层区域,而不是辅助函数自己的区域。一般规则是局部值不能逃出其区域。每个函数都定义一个区域,如果不作改变,将局部参数放进返回列表看起来就像一次逃逸。
Exclave 与局部返回值
讲者使用 `exclave_` 提前结束辅助函数的区域,再在调用方区域中分配列表。局部返回标注说明这种分配,并阻止结果逃逸。
关键字 `exclave_` 在构建列表之前提前结束 `greet` 的区域。参数 `z` 来自外层区域,因此在内层区域结束后使用它不会使它逃出该区域。新构建的列表也在 `greet` 区域之外,即调用方的区域内分配。
在我将返回值标为局部之前,仍然会出现错误。编译器报告一个值逃出了其区域;我认为这个例子中的错误信息有误导性,虽然类型错误本身有充分的理由。函数必须表明它可能在调用方区域分配,以便调用约定正确工作。

返回值上的 `local_` 标注传达两件事:函数可能在调用方区域分配,返回值也是局部的,不能逃出该区域。加上这个标注和 `exclave_` 后,示例可以编译。我可以对两个局部变量都使用 `greet`。
回顾一下:局部参数让 `greet` 能够接收 `x`;`exclave_` 在构建列表之前结束辅助函数的区域;局部返回标注则表明结果在父区域中局部分配,不能继续逃逸。编译器知道参数来自外层区域。
Exclave 内的局部别名
在示例中,参数的局部别名不能在 exclave 内使用。讲者指出局部参数和局部变量存在区别。
局部参数和局部变量之间有一个微妙的区别。如果我在 `greet` 内绑定 `let local_ a = z`,并在 exclave 中使用 `a`,编译器会拒绝:局部变量不能在那里使用。我们失去了这个绑定来自外层区域的信息。我会在之后的视频中进一步探讨这一区别。

同频道的其他文章
1:48:29EN → 简体中文
1:36:37EN → 简体中文
1:05:45EN → 简体中文
1:25:00EN → 简体中文
1:16:29EN → 简体中文
27:39EN → 简体中文