LWN:内核开发即将支持哪些 Rust 语言特性!

关注了就能看到更多这么棒的文章哦~

Upcoming Rust language features for kernel development

By Daroc Alden
October 8, 2025
Kangrejos
Gemini flash translation
https://lwn.net/Articles/1039073/

Rust for Linux 项目对 Rust 语言本身发展起到了积极作用,Rust 语言设计团队的联合负责人之一 Tyler Mandry 表示。他在 Kangrejos 2025 大会上发表了一场演讲,介绍了即将推出的 Rust 语言特性,并感谢 Rust for Linux 开发者们为推动这些特性所做的贡献。随后,Benno Lossin 和 Xiangfei Ding 深入探讨了他们在内核开发中最重要的三个语言特性上的工作:字段投影(field projections)、就地初始化(in-place initialization)和任意 self 类型(arbitrary self types)。

Mandry 提到,许多人认为 Rust 新语言特性的开发速度相当缓慢。部分原因在于 Rust 语言团队非常谨慎,以避免采纳不良设计。但最大的原因在于“关注度的统一”(alignment in attention)。Rust 项目由志愿者驱动,这意味着如果没有人专注于推动某个特定特性或一组相关特性,它们就会停滞不前。Mandry 解释说,Rust for Linux 项目在解决这个问题上非常有帮助,因为它激发了许多人的热情,并将精力集中在 Linux 内核所需的少数几个具体事物上。

Mandry 随后快速列举了一系列即将推出的语言特性,包括不带已知大小信息的类型(types without known size information)、引用计数改进(reference-counting improvements)、与 const 相同类型的用户定义函数修饰符(user-defined function modifiers of the same kind as const)等等。最后,他询问哪些特性对 Rust for Linux 最为重要,以及在场的内核开发者会如何对其进行优先级排序。除了稍后将讨论的三个特性,Lossin 表示该项目肯定希望能够在 trait 定义中编写可在编译时求值的函数(在 Rust 中称为 const 函数)。Danilo Krummrich 提出了特化(specialization)的需求,这立刻引来了 Lossin 的一声“哦不!”,因为这个特性在 Rust 类型系统中已经有近十年的问题历史。特化将允许单个 trait 存在两个重叠的实现,编译器会选择更具体的那个。Matthew Maurer 则提出了对编译器处理整数溢出(integer overflow)行为的某种控制能力。

最终,Miguel Ojeda 告诉 Mandry,优先级应该放在稳定 Rust for Linux 目前使用的不稳定语言特性上,其次是那些会改变项目代码结构方式的语言特性,最后是其他所有特性。接下来的两次演讲详细介绍了其中一些关键语言特性的当前状态和未来计划。

字段投影

字段投影(Field projection)指的是将指向结构体的指针转换为指向该结构体某个字段的指针。Rust 对于内置的引用和指针类型已经支持此功能,但对于用户定义的智能指针(smart-pointer)类型,它并非总能奏效。由于 Rust for Linux 开发者希望使用自定义智能指针来处理不可信数据(untrusted data)、引用计数(reference counting)、外部锁(external locking)以及相关的内核复杂性,因此他们将受益于一个通用的语言特性,该特性允许所有指针类型使用相同语法进行字段投影。Lossin 谈到了他在这方面的工作,这项工作自 Kangrejos 2022 以来一直在进行。“进展很大”,但工作仍处于设计阶段,还有一些细节有待解决。

Lossin 解释说,所有内置的字段投影都具有相同类型的签名。例如,将对象引用转换为其某个字段的引用,以及将对象的裸指针(raw pointer)转换为其某个字段的裸指针,它们的代码看起来不同,但具有相似的签名:

这个例子使用了相对较新的裸借用(raw borrow)语法。

Pin 类型在此处带来了些许复杂性。Rust 编译器默认情况下可以为了性能将结构体移动(move)到不同的内存位置。当结构体从 C 端被引用时,这种移动就不再适用,因此 Pin 类型被用来标记那些不应被移动的结构体。投影一个 Pin<MyStruct> (Lossin 已经更正 LWN:Pin 总是用来包裹一个指针类型,而不是直接包裹结构体) 可能会产生一个 Pin<&mut Field> 或一个普通的 &mut Field,这取决于该字段是否也是不应被移动的类型。因此,Lossin 表示,字段投影操作最通用的可能签名会是这样:

Container<'a, Struct>-> Output<'a, Field>

也就是说,给定一个包裹了结构体且必须在生命周期 a 内有效的指针类型,投影一个字段会得到一个包裹了该结构体字段的(可能不同的)输出指针类型,且在相同的生命周期内有效。Lossin 随后举例说明了如何支持这一特性可以极大地简化内核 Rust 绑定中读-复制-更新(read-copy-update,简称 RCU)支持的完全实现。

他解释说,RCU 机制保护读取者免受并发写入者的影响,但它不保护写入者之间互不影响。因此,在内核中,用互斥锁(mutex)保护某些数据,而该数据的某个频繁访问字段则由 RCU 保护,这种情况并不少见。这样,读取者依赖于开销低廉的 RCU 锁,写入者则使用互斥锁进行同步。将这种接口翻译成 Rust 会带来问题:Rust 不允许在未锁定 Mutex 之前访问其内部内容,因此这种模式的直接翻译将无法工作。它会强制 Rust 读取者为了读取 RCU 字段而锁定互斥锁,这将导致无法接受的性能下降。

然而,有了语言层面的通用字段投影,Rust for Linux 开发者就可以编写绑定,允许将一个 &Mutex<MyStruct> 投影成一个 &Rcu<Field> 而无需持有锁。在驱动程序代码中,尝试读取 RCU 保护的字段将看起来像正常的访问,就像在 C 语言中一样——但编译器仍然会检查其他非 RCU 保护的数据在没有持有互斥锁的情况下不会被触及。

Lossin 最后请在座的开发者关注该特性的跟踪问题(tracking issue),并提供反馈。Daniel Almeida 询问在主线内核(mainline kernel)之外测试该特性是否真的有帮助;Ojeda 肯定了这一点,因为这使得更容易向 Rust 团队提出稳定该特性的请求。Rust for Linux 项目正努力不使用任何新的不稳定特性(并使用等于或早于 Debian stable 所打包的 Rust 版本进行编译),因此该特性需要在 Debian 14(预计于 2027 年发布)之前完成并纳入其中,才能在内核代码中广泛使用。

Andreas Hindborg 问道:“我们能不能昨天就拥有这个功能,拜托了?”,引得大家一阵笑声。内核的 Rust 绑定已经包含大量编码了各种不变性的自定义指针(custom pointers);无论何时该特性可用于内核代码,都可能使它们在驱动程序代码中更容易使用。

任意 self 类型

紧接着,Ding 介绍了另一个为自定义指针(custom pointers)设计的便捷语言特性:任意 self 类型(arbitrary self types)。在 Rust 中,类型上的方法可以将类型本身的实例作为第一个参数,也可以是其引用。这样的方法可以使用 .method() 语法调用,而不是更通用的 Type::function() 语法。但内核 Rust 代码中智能指针的普遍使用意味着程序员通常不会持有普通的引用;相反,他们经常持有 Pin、Arc 或其他智能指针类型。

Ding 一直致力于的任意 self 类型提案将允许程序员编写接受智能指针而非普通引用的方法:

不幸的是,将其添加到编译器中并非一帆风顺。它与 Rust 现有的 Deref trait(正是它使得自定义智能指针成为可能)的交互使得实现变得复杂,因为在搜索匹配方法时并非所有类型信息都可用。目前,如果用户有一个 Pin<&mut MyStruct> 并对其调用一个方法,编译器会首先查找 Pin 的匹配方法。如果没有找到,它将尝试解引用(dereference)该类型,生成一个 &mut MyStruct。这个类型会被检查是否有匹配方法,然后最终再被解引用一次,生成一个 MyStruct。这个类型最终会有一个匹配方法,否则编译器会发出一个类型错误(type error)。

当这个过程开始检查与 MyStruct 相关的函数时,它已经丢弃了关于包装类型(wrapping types)的信息,而任意 self 类型的实现需要这些信息。Ding 花了几分钟解释了他尝试并放弃的解决问题的方法,然后重点介绍了当前的方法。他添加了另一个 trait——暂定名为 Receiver trait——用于标记可与任意 self 类型一起使用的类型。然后编译器可以尝试遵循 Receiver 实现链,然后再遵循 Deref 实现链。这意味着指针类型必须选择加入(opt into)作为任意 self 类型使用,但 Ding 认为这并非缺点。让指针类型的作者决定何时应支持新特性,可以消除许多关于意外引入向后兼容性(backward compatibility)问题的担忧。对于内核而言,这并没有真正构成障碍,因为 Rust 开发者可以随着遇到需要 Receiver 实现的情况而添加它们。

Ojeda 询问 Ding 认为最终确定任意 self 类型特性需要多长时间;特别是,它是否能在一年内准备好?Ding 同意一年内是可能的,但他需要 Rust 语言团队的支持才能实现。他希望在提交代码之前,使用 Crater(Rust 社区用于检查编译器更改是否破坏任何已发布 Rust 库的工具)对他的更改进行测试。Ojeda 表示可以帮助获取一台大型构建机器来完成这项工作,因为 Ding 之前在 Crater 运行时编译某些包时曾遇到内存需求问题。

就地初始化

Ding 想介绍的另一个话题是他关于就地初始化(in-place initialization)的工作。与讨论中的其他新语言特性一样,这并不能真正启用新的用例,但它确实能使常见的内核代码更简洁。目前,内核中的 Rust 代码使用 pin_init!() 宏来创建在初始化后固定在内存中的结构体(通过被 Pin 包装)。

pin_init!() 没有任何问题:“我们喜欢 pin_init!()!我们想把它做成一个语言特性。” 采用一个就地初始化语言特性也将有助于解决内核代码之外的一些尖锐问题;它可以使在堆上创建大型 Future 值(Future value)更符合人体工程学,并使一些 trait 变得dyn-兼容(dyn-compatible)。这个语言特性的确切设计更不确定;Ding 介绍了三种不同的提案,说明它可能如何运作。

Alice Ryhl 和 Lossin 提出的最简单方法是,在结构体初始化表达式之前添加一个新关键字 init,以要求编译器自动为内核的 PinInit trait 编写一个实现。这有一个很好的优点,即对语言的改变相当小,尽管它会锁定当前形式的 PinInit trait 的使用。

Taylor Cramer 提出的另一个解决方案,是向语言中引入一种新型的引用。Rust 现有的引用要么只能读取(=&=),要么可以读取和写入(=&mut=)。这个提案将添加第三种类型,=&out= 引用(out reference),它只能写入,不能读取。使用 &out 引用的唯一方式是写入它,或者使用投影(projection)将其分解为多个指向不同字段的 &out 引用。在这种方案下,就地初始化将看起来像是在堆上分配空间,然后返回一个 &out 引用。调用代码可以根据需要填充它,可能将子部分传递给其他函数。编译器会跟踪所有 &out 引用都被使用过,然后才允许代码获取堆分配的普通 &mut 引用。

然而,该提案远不如 Ryhl 和 Lossin 的方法成熟。Ding 后来告诉我,他和 Mandry 以及 Kangrejos 大会的其他编译器贡献者当天在演讲间隙,正在研究它将如何与 Rust 编译器的一些内部机制互动。会议结束时,他们对如何实现它有了一个粗略的想法,因此更详细的 out-pointer 提案可能很快就会发布。

最终的设计,借鉴了 C++ 的思想,将是一种保证优化,即构造一个新值然后立即将其移动到堆上,会使其一开始就在堆上构造。Ding 对最终提案的细节不太确定;他建议最好的前进方向可能是同时实现 PinInit 提案和 out-reference 提案,并观察每种方法在实践中的效果。

无论最终选择哪种方法,Mandry 关于 Rust for Linux 项目推动语言改进的观点显然是正确的。尽管这些特性仍处于早期阶段,但采纳它们可以显著简化涉及用户定义智能指针的代码,无论是在内核内部还是外部。

*更新:*自本文描述的演讲以来,字段投影(field projection)的工作已获得更新。Lossin 来信通知 LWN,所有结构体的所有字段现在都被认为是结构化固定(structurally pinned)的,因此投影一个 Pin 现在将始终产生一个 Pin<&mut Field> 或类似的值。

全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。

欢迎分享、转载及基于现有协议再创作~

长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值