7.6 KiB
02 · 名称与作用域
13/22 项已确认。当前推进 02-15;02-11 的特殊结构作用域仍需核对。
本页整理原讨论的问题、验收要求和候选方案。勾选代表讨论中已确认的规则,不代表编译器或运行时已经实现。
02-01 词法作用域
- 已确认
名称按定义位置的代码嵌套关系查找;调用者的局部变量不会参与被调用函数的名称查找。
02-02 普通变量的基本作用范围
- 已确认
不需要声明关键词;函数内普通变量采用 Python 式作用范围,普通 if、for 块不限制它。块内赋值后,块外可以使用,前提是该赋值确实发生。
02-03 let 临时绑定
- 已确认
let 声明的变量限定在所属块内;后续赋值仍操作该临时绑定,不扩大作用域;不提供 var。
02-04 内层遮蔽外层
- 已确认
内层 let 可以与外层变量同名。块内读写内层绑定,退出后恢复访问外层绑定,外层值不受这些修改影响。
02-05 同层重复 let
- 已确认
同一作用域重复声明同名 let 报错;修改已有变量使用普通赋值。
02-06 let 声明前的直接使用
- 已确认
整个块中的同名直接读写都认定为该临时绑定;声明前使用在编译检查阶段报错,不会转而访问外层同名变量。
02-07 分支与未初始化读取
- 已确认
确定不能保证读取前已赋值时,编译报错还是运行时报错;覆盖条件分支、可能执行零次的循环和提前退出。
**已确认规则:**采用编译时的确定赋值检查。每条能够到达读取位置的路径都必须先赋值;编译器无法证明这一点,就报错。
02-08 普通变量的声明前查找
- 已确认
确定函数后面出现普通赋值时,前面的同名读取算本地未初始化,还是可以访问外层绑定。
**已确认规则:**局部归属按整个函数确定,赋值前读取不能回退到外层同名变量。
02-09 同层名称冲突
- 已确认
明确参数、普通变量、let 之间哪些组合属于赋值、哪些属于重复声明或冲突。
**已确认规则:**参数后的普通赋值修改参数绑定;参数与函数体同层 let 同名、同层重复 let、重复参数名均编译报错;更内层 let 可以遮蔽;let 之前的同名赋值属于声明前访问,编译报错。
02-10 内层函数修改外层绑定
- 已确认
明确普通赋值默认修改哪一层,是否需要专门语法才能修改外层变量。
**已确认规则:**普通赋值默认建立或修改当前函数的局部绑定;通过 nonlocal 显式指定外层函数中已有的绑定。
02-11 各语法结构的作用域边界
- 待讨论
列清函数、独立块、循环及其他受支持结构各自如何引入绑定;未决定是否支持的结构标注依赖。依赖:语法设计。
02-12 循环绑定
- 已确认
确定循环变量和循环体中的 let 是每轮建立新绑定,还是多轮复用;说明闭包捕获后的结果。
**已确认规则:**每次进入循环体,创建这一轮自己的 let 绑定;普通变量继续复用所属函数中的绑定。循环外不能访问循环体内的 let。若支持循环头的 let,也采用每轮独立绑定的原则。
02-13 声明与初始化顺序
- 已确认
明确 let 能否不带初值、初始化表达式中的同名引用如何处理,以及受支持的复合声明按什么顺序建立绑定。依赖:语法设计。
**已确认规则:**允许先声明后赋值;初始化前读取自身报错;多目标声明或赋值先求完整右侧,再绑定目标。具体复合语法由语法设计对齐。
02-14 闭包捕获什么
- 已确认
确定默认捕获绑定还是当时的值,以及是否提供显式选择。
**已确认规则:**默认捕获绑定;外层重新赋值后,闭包读到新值。需要快照时手动建立独立绑定,不自动深拷贝对象。
02-15 闭包修改与共享
- 待讨论
确定多个闭包是否共享同一外层绑定、哪些修改允许、哪些情况报错。依赖:可变性规则。
02-16 逃逸闭包的绑定存续
- 待讨论
确定外层函数返回或代码块退出后,闭包仍可访问什么,以及如何保证访问有效。依赖:运行时实现、资源管理。
02-17 完整名称查找顺序
- 待讨论
明确函数外层、模块、内置名称的查找顺序,以及类成员是否参与裸名称查找。依赖:模块系统、对象与类型模型、内置库设计。
02-18 模块可见性
- 待讨论
明确默认私有还是公开、如何导出,以及访问不可见名称时的错误。依赖:模块系统。
02-19 导入名称的绑定与冲突
- 待讨论
明确导入名能否重新赋值、别名和重名如何处理、导入后观察到的是哪个绑定。依赖:模块系统。
02-20 跨模块初始化
- 待讨论
明确循环导入或模块尚未初始化时,读取其名称如何处理。依赖:模块系统、执行模型。
02-21 类型名与值名的关系
- 待讨论
确定是否共享命名空间、同名是否冲突、名称如何区分解析。依赖:类型系统。
02-22 最终一致性验收
- 待讨论
用一组正常与错误示例交叉检查上述规则,解决跨分类冲突,形成最终规则摘要。
当前问题的方案与语言对照
以下沿用原讨论材料;建议和参考语言的做法均不自动成为本语言规则。
接下来是 02-15:多个闭包能否共同读写同一个绑定。
fn example() {
count = 0
fn increase() {
nonlocal count
count = count + 1
}
fn read() {
return count
}
increase()
print(read()) // 如果允许共享,输出 1
}
各语言的相关规则:
| 语言 | 做法 | 收益与代价 |
|---|---|---|
| Python | 多个闭包可以共享绑定;修改外层函数绑定需要 nonlocal |
共享状态方便,修改也有明确标记;多个函数的行为会相互影响。官方说明 |
| JS / TS | 可以共享并修改同一绑定,不需要 nonlocal;TS 保持 JS 的运行语义 |
写法直接,读代码时需要留意哪些闭包会修改变量。官方手册 |
| Go | 外层函数与闭包共享变量,允许修改 | 共享直接;并发执行时需要另行协调访问。规范 |
| Rust | 借用捕获受访问限制约束;相互冲突的可变借用和只读借用不能同时存续。共享可变状态需要合适的显式机制 | 编译器能限制冲突访问,但需要更多所有权与借用规则。官方说明 |
我们有两个主要方向:
- 允许多个闭包共享读写:上面例子合法,输出
1;修改仍要求nonlocal。 - 限制共享可变捕获:某些同时持有读写访问的闭包组合被拒绝;具体限制需要与所有权模型一起设计。
我推荐 1,与前四种语言的方向一致,也能延续已经采用的 nonlocal。这里先决定单线程中的共享行为;并发访问规则属于并发模型。
讨论来源
《语言设计 02:名称与作用域》,整理日期 2026-10-28。章节编号沿用原清单。