Ballerina中isolated函数sum1编译报错sum2正常的原因
问题环境
- 运行版本:Ballerina 2201.1.0 (Swan Lake Update 1)
- 对应语言规范版本:2022R2
- 配套工具版本:Update Tool 1.3.9
问题表现
两个功能完全一致的isolated累加函数,编译结果完全不同:
sum1:通过数组reduce方法传入isolated匿名函数实现累加逻辑,编译时抛出invalid access of mutable storage in an 'isolated' function错误sum2:通过foreach循环遍历索引实现完全相同的累加逻辑,访问同一数组时无任何编译报错
复现代码
import ballerina/io; public function main() { io:println(sum1([1,2,3,4], [0,1])); io:println(sum2([1,2,3,4], [0,1])); } isolated function sum1(int[] numbers, int[] indexes) returns int { return indexes.reduce(isolated function (int accu, int index) returns int { // 此处触发编译错误 return accu + numbers[index]; }, 0); } isolated function sum2(int[] numbers, int[] indexes) returns int { int accu = 0; foreach int index in indexes { // 此处访问numbers无编译问题 accu += numbers[index]; } return accu; }
编译报错日志
$ bal build issue.bal Compiling source issue.bal ERROR [issue.bal:(11:23,11:30)] invalid access of mutable storage in an 'isolated' function error: compilation contains errors
版本校验日志
$ bal version Ballerina 2201.1.0 (Swan Lake Update 1) Language specification 2022R2 Update Tool 1.3.9
差异根本原因
这个编译差异和两个函数的业务逻辑是否一致没有关系,完全是Ballerina isolated 语义对不同场景下可变状态访问的校验规则不同导致的:
普通内部逻辑的访问规则
sum2里的foreach循环是sum2函数自身的顺序执行代码,跑在sum2自己的调用栈里。入参numbers是这次调用专属的可变数组,生命周期和sum2的单次调用绑定,不会被其他执行线程/strand并发访问,完全符合isolated函数的并发安全要求,所以直接访问不会触发编译报错。isolated闭包的捕获校验规则
sum1里传给reduce的那个isolated匿名函数是个独立的闭包,不是sum1顺序执行流的一部分。它会被当成参数传给reduce方法,由reduce内部逻辑调用,甚至有可能被传递到其他异步执行上下文跑。
按照2022R2版的语言规范,isolated闭包只能捕获两种值:要么是不可变的只读值,要么是本身标记为isolated根的可变存储。
这个闭包捕获的numbers是普通的可变int数组,既不是只读值,也不是isolated根,编译器就会判定它在isolated函数里非法访问了可变存储,直接抛编译错误。
本质上就是编译器做静态校验时的信任边界不一样:它信任函数内部写死的顺序、循环逻辑不会跑出当前函数调用栈,访问自己的入参肯定安全;但作为参数传递的isolated闭包有逃逸出当前调用栈的可能,必须严格卡捕获变量的隔离性要求,不会因为闭包写在sum1里面,就默认它能合法访问sum1的入参。
内容的提问来源于stack exchange,提问作者user272735

