不安全的 Rust 比 C 更难

本文介绍了作者为解决特定需求而编写的batch-channel,包括其设计目标、实现过程中的困难及相关优化等方面:

  • 设计目标:实现多生产者多消费者、同步异步支持、有界或无界、一次发送接收多个值且在稳态使用下无分配等。
  • 实现难点

    • 通道实现中的Waker问题waiting_for_elementsVec<Waker>,每次阻塞任务时都需分配内存,导致稳态下频繁分配和释放内存,影响性能。
    • 使用侵入式列表优化:尝试将Waker存储在阻塞的未来自身中以避免频繁分配,但在 Rust 中表达侵入式链表困难,如intrusive-collections中列表节点不能比列表存活更久等问题。
    • Pin相关问题WakerListWakerSlot构成自引用数据结构,需要使用unsafe代码,且Pin的特性导致语言假设值可移动,与自引用结构冲突,使公共 API awkward,如在函数参数的生命周期约束方面存在问题。
    • 处理未定义行为batch-channel的两个优化需要unsafe代码,为简化审计将其置于安全 API 和分离的 crate 中,Rust 中安全 Rust 无未定义行为,而unsafe Rust 可能导致未定义行为,可通过希望最好、仔细思考或自动化 sanitizers 处理,Rust 支持多种 sanitizers,其中 MIRI 对捕获 Rust 别名模型违规很重要。
    • Rust 别名模型问题:深入unsafe Rust 前应先了解相关指南,Rust 的别名规则与 C 不同,如在Box::leakBox::from_raw等操作中易出现 MIRI 失败的情况,在处理链接列表的自引用指针时,需注意避免形成冲突的&mut引用,可通过UnsafeCell引入“mutability barrier”来解决,还可避免创建引用而完全在指针读写和偏移的域中操作。
  • 相关工作及研究:介绍了多个涉及侵入式列表的 crate,如futures-intrusive等,以及pinned-mutex crate 用于获取Pin<&mut T>,还提到 Rust-for-Linux 项目对Pin和自引用数据结构的关注及相关研究,如pinned initialization problem等。
  • 结论:尽管编写过程痛苦,但最终得到了安全高效的 APIwakerset,从中学会要谨慎使用unsafe,明白 Rust 中引用比 C 中的指针更危险,Pin语法不佳但有望解决,UnsafeCell对于侵入式结构很重要,不清楚如何静态约束侵入式结构的生命周期关系,MIRI 在多线程压力测试中很关键,将经历写成文字和写代码一样困难。
阅读 19
0 条评论