8.5 KiB
13 · 外部交互
0/22 项已确认。当前推进 13-01;本分类尚未完成选择。
本页整理原讨论的问题、验收要求和候选方案。勾选代表讨论中已确认的规则,不代表编译器或运行时已经实现。
13-01 外部交互的默认安全边界
- 待讨论
明确普通代码默认能否接触外部地址、依靠谁保证安全,以及是否提供不安全入口。
13-02 I/O 的基础接口单位
- 待讨论
明确字节、文本、流、消息之间的基础关系,以及哪些能力由上层封装提供。
13-03 I/O 的执行方式
- 待讨论
明确阻塞、异步是否支持,默认方式,以及超时和取消对操作结果的影响。依赖【并发与执行模型】。
13-04 文件与路径接口
- 待讨论
明确路径如何表示,文件如何打开,以及创建、覆盖、追加和访问失败的规则。
13-05 网络接口范围
- 待讨论
明确本轮支持哪些通信抽象,以及连接、监听、半关闭和断开的基本行为;明确不支持的能力。
13-06 I/O 的部分成功与结束状态
- 待讨论
明确短读、短写、EOF、暂不可用、部分完成后失败如何区分和报告。
13-07 外部资源的关闭规则
- 待讨论
明确谁负责关闭、重复关闭和关闭后使用的行为,以及自动清理是否保证及时发生。依赖【资源管理】。
13-08 FFI 支持范围与调用约定
- 待讨论
明确支持哪些外部调用目标、是否支持向外导出函数,以及变参等特殊接口的支持边界。依赖【目标平台与执行方式】。
13-09 外部声明与符号解析
- 待讨论
明确外部函数如何声明、库和符号何时解析,以及缺失、重复或声明不匹配时的行为。依赖【模块系统】。
13-10 基础类型映射
- 待讨论
明确整数宽度与符号、浮点、布尔、空值、指针的对应关系,以及不能表示时的处理。
13-11 复合类型与数据布局
- 待讨论
明确结构体、数组、枚举等如何传递,哪些布局可以公开,以及大小、对齐、填充如何确定。依赖【类型与数据模型】。
13-12 字符串与编码
- 待讨论
明确编码、长度单位、终止符、内嵌零值和非法编码的处理,以及转换责任。
13-13 缓冲区访问契约
- 待讨论
明确只读或可写、连续性、复制或共享、借用期限,以及外部保存地址时的要求。依赖【内存管理】。
13-14 内存所有权与分配器配对
- 待讨论
明确跨边界分配、转移、释放的责任,以及由哪个分配器或释放函数回收。
13-15 不透明句柄与运行时值映射
- 待讨论
明确外部资源或复杂值如何表示,是否允许直接暴露语言内部值,以及无效句柄的处理。依赖【对象与类型模型】。
13-16 回调的注册与生命周期
- 待讨论
明确回调如何传递、保存、注销,如何保活其上下文,以及注销后迟到调用的处理。
13-17 外部线程与重入
- 待讨论
明确哪些线程能进入运行时、是否允许嵌套调用,以及违反约束时的行为。依赖【并发与运行时】。
13-18 跨边界错误传递
- 待讨论
明确错误码、异常和取消怎样转换,是否允许栈展开越过边界,以及失败时如何清理。依赖【错误机制】。
13-19 库卸载与运行时退出
- 待讨论
明确仍有句柄、回调或进行中的操作时,能否卸载库或退出运行时,以及处理顺序。
13-20 跨进程与跨运行时的数据交换
- 待讨论
明确是否提供通用交换格式或协议;若提供,规定支持的值、身份或引用关系及转换失败行为。
13-21 平台差异与接口版本兼容
- 待讨论
明确哪些保证跨平台成立,不兼容布局或库版本如何识别,以及无法验证时由谁承担责任。
13-22 跨分类一致性验收
- 待讨论
用文件读取、外部缓冲区、外部返回资源、异步回调四类完整场景,检查上述规则与相关分类是否一致,消除阻塞和冲突。
当前问题的方案与语言对照
以下沿用原讨论材料;建议和参考语言的做法均不自动成为本语言规则。
当前仍是 13-01:外部交互的默认安全边界。要决定的是:普通代码调用外部库时,内存有效性由谁保证,哪些责任交给底层绑定作者。
仍用同一个例子:把字节缓冲区交给外部库,外部库可能立即读取,也可能保存地址、稍后读取。
Python:底层接口可以绕过通常的安全保护。
以 CPython 提供的 ctypes 为例,可以创建缓冲区、传递指针、声明外部函数参数类型。但声明参数类型不等于证明外部访问安全;错误的地址或使用方式可能导致内存损坏、进程崩溃。这种方式方便直接接入本地库,代价是调用者或封装作者承担较多责任。对我们的启发是:“能声明函数签名”与“能保证生命周期正确”需要分开设计。 官方文档
JS:这里以 Node.js 的原生扩展机制为例,属于宿主提供的能力。
Node-API 可以把外部内存包装成 JS 可访问的 Buffer,并关联回收时调用的清理回调。例如,扩展分配一块内存,再将它暴露给 JS;扩展仍需正确安排其有效期和释放。好处是普通 JS 使用者可以通过熟悉的值访问数据;代价是底层扩展仍然承担内存责任,不同宿主的支持也可能不同。启发是:上层接口可以简单,但底层责任必须明确。 Node-API 文档
TS:增加静态检查,不另提供一套运行时内存保证。
例如,把接口声明成接受 Uint8Array,可以帮助检查调用处的类型,却不能证明原生扩展没有保存过期地址。类型注解会被擦除,运行时行为仍取决于 JS 宿主和扩展。好处是提前发现一部分接口误用;局限是类型声明本身不能验证外部实现是否守约。这也提醒我们,方案 B 不能只停留在“多写几个类型”。TS 官方说明
Go:cgo 明确限制跨边界指针,并提供复制转换。
例如,C.CBytes(data) 会复制字节到 C 堆,由调用者安排释放;直接向 C 传递 Go 内存则受指针和固定内存规则约束,不能默认让 C 无限期保存。好处是既支持复制也支持受约束共享;代价是绑定作者需要理解两边的内存规则。启发是:复制与共享可以并存,但应该让使用者知道自己选择了哪种契约。 cgo 官方文档
Rust:用显式不安全边界承接无法自动检查的责任,再封装安全接口。
例如,一个接受“地址+长度”的不安全外部函数,可以被包装成接受字节切片的安全函数;包装作者必须确保长度正确、调用期间内存有效,并确认外部不会在约定期限外使用地址。好处是普通调用者不必反复证明这些条件;代价是包装必须正确,否则安全接口也可能失去保证。启发是:区分“使用接口的人”与“为接口安全性负责的人”。 Rust 官方说明
据此,我们的三个选项仍然是:
| 方案 | 普通使用者默认面对的接口 | 主要代价 |
|---|---|---|
| A:直接控制地址 | 自己保证地址、长度和生命周期 | 调用方便直接,但内存责任分散到调用处 |
| B:通过契约绑定 | 使用声明好借用、保留或所有权转移规则的接口 | 绑定设计更复杂,需要信任绑定和外部库守约 |
| C:主要使用副本与句柄 | 交换独立数据,通过受管理操作访问资源 | 复制与转换开销,部分底层接口较难表达 |
我仍建议 B,并保留显式不安全入口供底层绑定使用。 这样可以把责任集中在绑定层,缓冲区采用复制还是共享则留给 13-13 决定。其保证以绑定和外部库遵守契约为前提;同进程原生代码违约时,不能承诺一定转成可捕获错误,也可能损坏或终止进程。
讨论来源
《语言设计 13:外部交互》,整理日期 2026-10-28。章节编号沿用原清单。