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 函数。

不可移动类型 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 的模式。
  • 通常销毁需要解构,或者在定义模块中提供专门的消费函数。

向后兼容与 std 影响

  • 向后兼容是重大风险:
    • 现有泛型隐式假定一切都可以移动并可被丢弃。
    • 当某些类型是 !Move 时,诸如 Iterator::ItemDeref::Target 的 trait 会变得有问题。
    • Vec<T> 这样的集合本质上会移动元素;它们大概不能支持 !Move 类型,或者只能通过受限 API 支持。
  • 版本(edition)有帮助,但无法解决所有问题;对默认值和边界(T: Move vs T: ?Move)的谨慎设计至关重要,而且并不简单。

与代数效应和 C++ 的关系

  • 有人把这些 trait 看作像效应:drop<T>move<T>forget<T> 的行为就像附着在函数上的隐式效应。
  • 也有人认为这主要是关于类型属性,并可能与未来的效应系统相关。
  • 与 C++ 的比较:
    • Rust 并没有添加隐式的 move/copy 构造函数。
    • 不可移动性和有保证的析构器使与 C++ 的互操作,以及某些模式(自引用、复杂对象图)更容易,但语义更严格且更显式。

状态与不确定性

  • 这是一个 项目目标,不是已被接受的语言变更。
  • 它与 pin 人体工学目标相竞争;很多人预期如果只能保留一个,不可移动类型会胜出,但:
    • 这个变更影响面很广,而且很复杂。
    • 语义、向后兼容性和 std 集成都仍未定,并且仍可能使设计流产或被大幅重塑。