169 lines
8.8 KiB
Markdown
169 lines
8.8 KiB
Markdown
# 11 · 模块与程序执行
|
||
|
||
0/22 项已确认。当前推进 11-01;本分类尚未完成选择。
|
||
|
||
本页整理原讨论的问题、验收要求和候选方案。勾选代表讨论中已确认的规则,不代表编译器或运行时已经实现。
|
||
|
||
## 11-01 模块的职责与顶层内容
|
||
|
||
- [ ] 待讨论
|
||
|
||
明确模块主要承载声明、可执行初始化,还是可实例化的定义;说明导入与执行的关系。
|
||
|
||
## 11-02 模块的组织单位
|
||
|
||
- [ ] 待讨论
|
||
|
||
明确模块与文件、目录、包的对应关系,以及嵌套模块、多文件模块是否支持;说明不合法的组织形式。
|
||
|
||
## 11-03 模块身份
|
||
|
||
- [ ] 待讨论
|
||
|
||
明确判断“同一个模块”的依据;覆盖路径别名、重复引用及不同版本,说明身份冲突如何处理。
|
||
|
||
## 11-04 名称与路径解析
|
||
|
||
- [ ] 待讨论
|
||
|
||
明确绝对引用、相对引用、搜索位置及优先级;说明找不到目标、存在多个候选时的行为。
|
||
|
||
## 11-05 依赖与版本选择
|
||
|
||
- [ ] 待讨论
|
||
|
||
明确依赖由谁声明、版本由谁选择,能否同时使用多个版本;划清语言、构建工具和包管理器的职责。
|
||
|
||
## 11-06 导出与可见性
|
||
|
||
- [ ] 待讨论
|
||
|
||
明确默认公开范围、显式导出及内部成员的访问规则;说明越权引用如何失败。依赖【名称与作用域】。
|
||
|
||
## 11-07 导入形式与名称冲突
|
||
|
||
- [ ] 待讨论
|
||
|
||
明确整体导入、成员导入、别名、重新导出和批量导入的支持范围;说明重名与遮蔽规则。
|
||
|
||
## 11-08 跨模块绑定语义
|
||
|
||
- [ ] 待讨论
|
||
|
||
明确导入获得值、持续关联的绑定,还是其他形式;说明重新赋值和修改共享对象的区别。依赖【名称与作用域】【对象与类型模型】。
|
||
|
||
## 11-09 依赖检查与条件导入
|
||
|
||
- [ ] 待讨论
|
||
|
||
明确依赖必须何时可用,是否支持可选或条件导入;说明未使用但缺失的依赖是否导致失败。依赖【编译与执行方式】。
|
||
|
||
## 11-10 初始化触发与次数
|
||
|
||
- [ ] 待讨论
|
||
|
||
明确初始化何时发生、何种范围内执行几次,以及重复导入的行为。
|
||
|
||
## 11-11 初始化顺序与并发
|
||
|
||
- [ ] 待讨论
|
||
|
||
明确依赖之间的初始化顺序,是否允许并发或异步初始化;说明顺序不确定时有哪些保证。依赖【并发与异步】。
|
||
|
||
## 11-12 循环依赖
|
||
|
||
- [ ] 待讨论
|
||
|
||
明确允许哪些循环,何时检测;说明访问尚未准备好的定义或状态如何处理。
|
||
|
||
## 11-13 初始化失败
|
||
|
||
- [ ] 待讨论
|
||
|
||
明确失败后模块处于什么状态,能否重试,如何处理已发生的副作用,以及依赖方会得到什么结果。依赖【错误处理】。
|
||
|
||
## 11-14 模块状态与隔离
|
||
|
||
- [ ] 待讨论
|
||
|
||
明确是否允许可变模块状态、由谁持有、何时结束,以及多个程序环境能否相互隔离。依赖【内存与资源管理】。
|
||
|
||
## 11-15 程序入口的确定
|
||
|
||
- [ ] 待讨论
|
||
|
||
明确入口是指定函数、指定模块、顶层代码还是其他形式;说明缺失或存在多个入口时如何处理。
|
||
|
||
## 11-16 入口的输入输出契约
|
||
|
||
- [ ] 待讨论
|
||
|
||
明确参数如何传入、入口允许返回什么,以及如何形成退出结果;说明不合法的入口签名。依赖【类型系统】【标准库与宿主接口】。
|
||
|
||
## 11-17 启动流程
|
||
|
||
- [ ] 待讨论
|
||
|
||
明确依赖准备、模块初始化与入口执行的先后关系;说明启动中途失败时入口和清理是否执行。
|
||
|
||
## 11-18 动态装载与模块生命周期
|
||
|
||
- [ ] 待讨论
|
||
|
||
明确是否支持运行中加载、重新加载和卸载;如支持,说明旧引用、状态与资源的去向;如不支持,记录限制。
|
||
|
||
## 11-19 正常结束
|
||
|
||
- [ ] 待讨论
|
||
|
||
明确入口返回、显式退出与后台工作之间的关系;说明哪些条件代表程序真正结束。依赖【并发与异步】。
|
||
|
||
## 11-20 异常与外部终止
|
||
|
||
- [ ] 待讨论
|
||
|
||
明确未处理错误、取消、外部终止请求和强制终止分别有什么保证;说明错误报告与退出结果。依赖【错误处理】【并发与异步】【宿主接口】。
|
||
|
||
## 11-21 清理协议与顺序
|
||
|
||
- [ ] 待讨论
|
||
|
||
明确清理动作如何注册或归属、执行顺序、能否异步,以及清理自身失败或重复触发的处理。依赖【内存与资源管理】【错误处理】。
|
||
|
||
## 11-22 跨分类一致性验收
|
||
|
||
- [ ] 待讨论
|
||
|
||
用普通启动、共享依赖、循环依赖、初始化失败和退出清理等完整场景检查规则;相关分类决定已对齐,且没有未解决的阻塞,才可完成。
|
||
|
||
## 当前问题的方案与语言对照
|
||
|
||
以下沿用原讨论材料;建议和参考语言的做法均不自动成为本语言规则。
|
||
|
||
当前仍是 **11-01:模块的职责与顶层内容**。要决定的是:模块顶层能否执行读取配置、打开连接等动作,以及这些动作是否由导入自动触发。
|
||
|
||
相关语言的对照如下。下述好处与代价是设计取舍分析,不是在断言它们的历史设计动机。
|
||
|
||
| 语言 | 规则与具体行为 | 取舍及对我们的启发 |
|
||
|---|---|---|
|
||
| **Python** | 对普通源码模块,常规首次导入会执行模块代码。例如顶层 `current = read_config()` 会在导入时调用函数;后续命中模块缓存时通常不再执行。这里说的是 Python 导入规则,并非 CPython 独有的优化。[官方参考](https://docs.python.org/3/reference/import.html#loading) | 顶层写法直接,容易提供共享状态;但一次导入可能读文件、修改环境或抛出异常。对应我们的 **B**。 |
|
||
| **JS:ES Modules** | 模块允许顶层语句。例如模块中写 `console.log("loaded")`,该模块被求值时就会打印;普通静态导入会使依赖参与求值。导入声明的连接过程与模块代码求值是不同阶段。这里专指 ESM,CommonJS 另有规则。[ECMAScript 规范](https://tc39.es/ecma262/multipage/ecmascript-language-scripts-and-modules.html#sec-modules) | 能自动执行依赖的准备动作;也需要处理副作用、初始化顺序和失败。提醒我们区分“找到并连接定义”和“执行初始化”。 |
|
||
| **TS** | 运行时模块行为由实际采用的 JS 模块格式和宿主决定,TS 不提供独立运行时。`import type { Config } ...` 会从输出 JS 中消除,因此这条导入本身不会触发运行时加载。[官方文档](https://www.typescriptlang.org/docs/handbook/modules/reference#type-only-imports-and-exports) | 类型依赖可以与运行时依赖分开,但要明确两者的区别。启发是:需要一个类型,不一定需要运行其所属模块;是否采用该机制留给后续任务。 |
|
||
| **Go** | 包顶层不能任意写执行语句,但变量初始化可以调用函数,如 `var current = readConfig()`;还可以写自动执行的 `func init()`。程序启动时先初始化依赖包,再运行入口。[语言规范](https://go.dev/ref/spec#Package_initialization) | 顶层结构规整,仍可自动准备共享状态;副作用仍可能藏在变量初始化里。说明**“顶层只准声明”并不等于“没有自动执行”**。 |
|
||
| **Rust** | 模块是定义项的容器,没有普通的“导入即执行模块体”机制。直接在模块体中写 `read_config();` 不合法;普通 `static` 的初始化表达式要求能在编译期求值,不能直接读取运行时配置文件。可把读取动作放入函数,由调用者调用。[模块规则](https://doc.rust-lang.org/reference/items/modules.html)、[静态项规则](https://doc.rust-lang.org/reference/items/static-items.html) | 普通模块使用不会自动读取配置,启动动作更明确;调用者需要组织初始化。对应我们的 **A** 方向。 |
|
||
|
||
还有一点需要澄清:**A、B 两种模块都可以导出 `create(config)` 这样的普通函数,用它创建多个独立状态。** 只有当我们希望语言本身支持“创建模块实例、给实例连接依赖”时,才需要之前的 **C**;普通对象或函数已经能解决不少隔离需求。
|
||
|
||
因此当前三个选择保持不变:
|
||
|
||
- **A.显式调用**:模块提供定义;读取配置等运行时动作放入函数,由调用者启动。还需明确允许哪些顶层初始化表达式,才能保证声明中不会暗藏这些动作。
|
||
- **B.自动初始化**:模块可以带运行时初始化,使用模块时由系统安排执行;触发时机、次数和失败处理分别在后续任务决定。
|
||
- **C.显式模块实例**:导入定义后,由调用者创建带状态的模块实例;语言需要提供实例创建和依赖连接规则。
|
||
|
||
**我暂建议 A**:它让初始化失败发生在明确的调用处,也方便调用者决定状态如何共享。代价是程序需要多写一些启动与状态传递代码。如果你希望模块直接提供“准备好的共享服务”,B 会更顺手。这个建议尚未成为决定。
|
||
|
||
## 讨论来源
|
||
|
||
《语言设计 11:模块与程序执行》,整理日期 2026-10-28。章节编号沿用原清单。
|
||
|