Rust 项目目标:不可移动类型与有保证的析构器
Rust 维护者正在探索诸如 `!Move` 和 `!Forget` 之类的新类型级 trait,以支持不可移动类型和有保证的析构器,目标是解决 async、自引用结构和内存泄漏等长期问题。评论者指出,这可能带来更安全的结构化并发(例如保证清理的作用域 async 任务)以及更具表现力的 API,比如线性的“必须使用”资源,同时也强调了与现有 trait、集合以及 `Pin` 模型之间严重的向后兼容挑战。人们普遍对这些特性可能解锁的能力感到兴奋,但也承认该设计很复杂、尚未定稿,并且可能需要在“pin 人体工学”等替代提案之间做出取舍。
高层思路
- 该提案引入诸如
!Move、!Forget,以及最终的!Destruct之类的 trait,用于:- 将不可移动性变成 类型 的属性,而不是 位置 的属性(取代
Pin的很大一部分用途)。 - 保证某些类型的析构器会运行(或者保证无法泄漏)。
- 为线性 / 必须移动类型奠定基础。
- 将不可移动性变成 类型 的属性,而不是 位置 的属性(取代
Async Rust 与结构化并发
- 主要动机是改进 async / 结构化并发:
- 目前,async 任务可以被任意取消,因此析构器可能永远不会运行。
- 这阻碍了完全安全的“作用域任务”,类似于作用域线程。
- 通过
!Forget,任务句柄可以被保证会被丢弃,从而让有作用域的 async 既安全又符合人体工学。
- 还可帮助:
- 递归 async 函数(不再需要
Box::pin之类的技巧)。 - 借用外部作用域而不是复制的 async 函数。
- 递归 async 函数(不再需要
不可移动类型 vs Pin
Pin被广泛认为令人困惑,而且像是一种“技巧”:- 它固定的是 位置,并且有棘手的
Unpin语义和投影规则。 - 使用
Pin的底层 async 代码不够符合人体工学,并且依赖unsafe。
- 它固定的是 位置,并且有棘手的
!Move会把不可移动性作为类型内在属性:- 支持安全的自引用以及
Pin不能完全支持的复杂模式。
- 支持安全的自引用以及
- 还有一条“固定位置”/ pin 人体工学改进路径;该提案明确是一个替代方案,并旨在长期废弃
Pin。
泄漏控制与 !Forget
!Forget旨在确保在存储被重用之前析构器会运行。- 通过
mem::forget或引用环造成的泄漏:- 拟通过要求
Rc/Arc和类似类型具备T: Forget来加以约束。 - 无限循环或把值发送到其他线程不被视为“忘记”,因为作用域并未结束。
- 拟通过要求
线性 / 必须移动类型(!Destruct)
- 这会强制显式的销毁路径:
- 像事务这样的 API 可以在静态上要求
commit/rollback二者必居其一。 - 可以避免“静默回滚”或在
Drop中 panic 的模式。
- 像事务这样的 API 可以在静态上要求
- 通常销毁需要解构,或者在定义模块中提供专门的消费函数。
向后兼容与 std 影响
- 向后兼容是重大风险:
- 现有泛型隐式假定一切都可以移动并可被丢弃。
- 当某些类型是
!Move时,诸如Iterator::Item和Deref::Target的 trait 会变得有问题。 - 像
Vec<T>这样的集合本质上会移动元素;它们大概不能支持!Move类型,或者只能通过受限 API 支持。
- 版本(edition)有帮助,但无法解决所有问题;对默认值和边界(
T: MovevsT: ?Move)的谨慎设计至关重要,而且并不简单。
与代数效应和 C++ 的关系
- 有人把这些 trait 看作像效应:
drop<T>、move<T>、forget<T>的行为就像附着在函数上的隐式效应。 - 也有人认为这主要是关于类型属性,并可能与未来的效应系统相关。
- 与 C++ 的比较:
- Rust 并没有添加隐式的 move/copy 构造函数。
- 不可移动性和有保证的析构器使与 C++ 的互操作,以及某些模式(自引用、复杂对象图)更容易,但语义更严格且更显式。
状态与不确定性
- 这是一个 项目目标,不是已被接受的语言变更。
- 它与 pin 人体工学目标相竞争;很多人预期如果只能保留一个,不可移动类型会胜出,但:
- 这个变更影响面很广,而且很复杂。
- 语义、向后兼容性和 std 集成都仍未定,并且仍可能使设计流产或被大幅重塑。