即将推出的适用于内核开发的Rust语言特性

Rust语言设计团队联合负责人泰勒·曼德里表示,Rust for Linux项目对Rust语言发展大有裨益。他在Kangrejos 2025大会上发表演讲,介绍了即将推出的Rust语言特性,并感谢Rust for Linux开发者推动这些特性落地。随后,Benno Lossin和Xiangfei Ding深入阐述了他们针对内核开发的三项核心语言特性:字段投影、就地初始化及任意自类型。

曼德里指出,许多人认为Rust的新语言特性开发速度较慢。部分原因在于Rust语言团队竭力避免固化不良设计,但最关键的因素在于“关注度的对齐”。曼德里解释道,Rust项目由志愿者驱动,这意味着若缺乏专注推进特定功能或相关功能集的开发者,这些功能便会停滞不前。而Rust for Linux项目对此起到了关键作用——它既能激发众多开发者的热情,又能将精力聚焦于Linux内核所需的少数核心功能。

图0:即将推出的适用于内核开发的Rust语言特性

曼德里随后快速列举了即将推出的语言特性,包括:未知大小类型的支持 引用计数改进与const同类的用户自定义函数修饰符等。最后他询问这些特性中哪些对Rust在Linux平台至关重要,以及在座内核开发者将如何排序优先级。除稍后讨论的三项特性外,洛辛强调项目亟需在特质定义中支持编译时可评估函数(Rust中称为const函数)。Danilo Krummrich提出特化需求,此举立即引发Lossin的“糟了!”惊呼——该特性近十年间持续给Rust类型系统带来困扰。特化机制允许单一特质存在两个重叠实现,编译器将选择更具体的实现。Matthew Maurer 提出需要某种机制来控制编译器对整数溢出的处理方式。

元素周期表

最终,Miguel Ojeda 告诉 Mandry,当前优先级应是稳定 Rust for Linux 项目现有的不稳定语言特性,其次是可能改变项目代码结构的语言特性,最后才是其他所有内容。接下来的两场演讲详细阐述了部分关键语言特性的现状与未来规划。

字段投影

字段投影指将结构体指针转换为结构体字段指针的技术。Rust 内置引用和指针类型已支持此功能,但用户自定义智能指针类型未必能实现。鉴于 Linux 开发者需要自定义智能指针来处理不可信数据、引用计数、外部锁定及相关内核复杂性,若能通过统一语法为所有指针类型实现字段投影,将极大提升开发效率。Lossin 介绍了自 Kangrejos 2022 以来持续推进的该问题研究。目前已取得“大量进展”,但工作仍处于设计阶段,尚有若干细节待完善。

洛辛解释道,内置字段投影函数均采用同类型的签名。例如将对象引用转换为其字段引用的代码,与将对象原始指针转换为其字段原始指针的代码,虽然外观不同,但签名结构相似:

    fn project_reference(r: &MyStruct) -> &Field {
        &r.field
    }

    unsafe fn project_pointer(r: *mut MyStruct) -> * mut Field {
        unsafe { &raw mut (*r).field }
    }

    // 对应的 C 语言代码如下:
    struct field *project(struct my * r) {
        return &(r->field);
    }

此示例采用了相对较新的 原始借用 语法。

Pin 类型给事情添了点麻烦。Rust 编译器默认会为性能考虑自由移动结构体。但当结构体被 C 端引用时这种做法行不通,因此 Pin 类型被用来标记不应移动的结构体。投影 Pin Pin<&mut MyStruct> [Lossin 向 LWN 提交更正:Pin 始终用于包装指针类型,而非直接包装结构体] 可能生成 Pin<&mut Field> 或普通 &mut Field,取决于字段本身是否也属于不应移动的类型。因此 Lossin 指出,字段投影操作的最通用签名应类似如下:

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

即:对于包裹结构体且生命周期 a 有效的指针类型,投影操作将返回包裹该结构体字段的(可能不同)输出指针类型,且具有相同生命周期。Lossin 随后举例说明,支持此功能将极大简化内核 Rust 绑定中读取-复制-更新(RCU)机制的完整实现。

图1:即将推出的适用于内核开发的Rust语言特性

他解释道,RCU机制能保护读取者免受并发写入者的影响,但无法保护写入者彼此之间的冲突。因此在内核中,常见做法是使用互斥锁保护数据,同时将频繁访问的字段交由RCU保护。这样读取者依赖成本较低的RCU锁,写入者则通过互斥锁实现相互同步。将该接口移植到Rust会遇到问题:Rust禁止在未加锁的情况下访问互斥锁内部内容,因此直接移植该模式行不通。这将迫使Rust读取者为读取RCU字段而锁定互斥锁,导致不可接受的性能开销。

但借助语言的泛化字段投影功能,Linux版Rust开发者可编写绑定,允许将&Mutex投影为&Rcu而不必持有锁。在驱动程序代码中,读取受RCU保护的字段将如同C语言中的普通访问——但编译器仍会检查未持有互斥锁时是否触及了其他非RCU保护的数据。

Lossin最后呼吁与会开发者持续关注该功能的跟踪问题,并提供反馈。Daniel Almeida质疑在主线内核外测试该功能是否真正有效;Ojeda肯定其价值,认为这能更轻松地向Rust团队论证功能稳定化的必要性。Linux版Rust项目坚持不采用任何新不稳定特性(且编译时使用的Rust版本需等于或早于Debian稳定版所打包的版本),因此该特性需在完成后纳入Debian 14(预计2027年发布)才能在内核代码中广泛使用。

Andreas Hindborg打趣道:“能否昨天就实现?”引得众人会心一笑。内核的Rust绑定已包含大量编码各类不变量的自定义指针;该特性一旦在内核代码中可用,将极大简化驱动程序中的指针操作。

任意自类型

丁随即介绍了另一项提升自定义指针使用体验的语言特性:任意自类型。在Rust中,类型方法的首个参数既可以是该类型的对象,也可以是该类型的引用。此类方法可通过.method()语法调用,而非更通用的Type::function()语法。然而内核Rust代码中智能指针的普及意味着程序员通常无法直接使用普通引用,而往往需要使用Pin、Arc或其他智能指针类型。

丁正在推进的自定义类型提案将允许程序员编写接受智能指针而非普通引用作为参数的方法:

    impl MyStruct {
        fn method(self: Pin<&mut MyStruct>) {}
    }

遗憾的是,将此功能添加到编译器并非易事。由于Rust现有的Deref特性使得自定义智能指针成为可能,但在搜索匹配方法时并非所有类型信息都可用,这使得实现过程变得复杂。当前,当用户持有 Pin<&mut MyStruct> 并调用其方法时,编译器会优先查找 Pin 的匹配方法。若未找到,则尝试解引用该类型,生成 &mut MyStruct。该类型将被检查匹配方法,随后进行最终解引用生成 MyStruct。此时该类型最终会找到匹配方法,否则编译器将报出类型错误。

图2:即将推出的适用于内核开发的Rust语言特性

当该过程开始检查与 MyStruct 关联的函数时,它已丢弃了任意自类型实现所需的包装类型信息。丁向飞花了几分钟说明他尝试过但放弃的修正方案,随后聚焦于当前方案。他新增了一个暂定名为 Receiver 的特质,用于标记可与任意自类型配合使用的类型。编译器现在可以优先追踪Receiver实现链,再处理Deref实现链。这意味着指针类型必须主动选择支持任意自类型功能,但丁认为这并非缺点。让指针类型作者自主决定是否支持新特性,能有效规避意外引入向后兼容问题的风险。对内核而言,这其实不构成障碍,因为Rust开发者可在遇到需求时随时添加接收器实现。

奥赫达询问丁认为完成任意自身类型特性需要多久时间,特别是能否在一年内完成?丁表示一年内完成是可能的,但需要Rust语言团队的支持才能实现。他希望在提交代码前,使用 Rust 社区用于检测编译器变更是否破坏已发布库的工具 Crater 进行验证。Ojeda 主动提供大型构建机器协助,因丁此前在运行 Crater 时曾因内存需求无法编译某些包而受阻。

就地初始化

丁还希望讨论其就地初始化功能的开发进展。与其他讨论中的新语言特性类似,该功能虽未开辟全新应用场景,但能显著优化常见内核代码的可读性。当前内核中的Rust代码使用pin_init!()宏创建结构体,这些结构体在初始化后通过Pin包装实现就地固定。

pin_init!()本身并无问题:“我们热爱pin_init!()!希望将其提升为语言特性。”引入原位初始化语言特性还能解决内核代码外的若干尖锐问题:它能更便捷地在堆上创建大型Future值,并使某些特质具备动态兼容性。该语言特性的具体设计尚无定论;丁博士介绍了三种不同的实现方案。

最简方案由Alice Ryhl和Lossin提出:在结构初始化表达式前添加新关键字init,要求编译器自动生成内核PinInit特性的实现。该方案的优势在于对语言的改动极小,但会将PinInit特性的使用锁定在当前形态。

另一种由泰勒·克莱默提出的解决方案,计划在语言中引入一种新型引用类型。Rust现有的引用类型可分为读取型(&)和读写型(&mut)。该提案将新增第三种类型&out,仅支持写入操作而禁止读取。使用&out引用的唯一方式是直接写入,或通过投影将其拆解为多个指向不同字段的&out引用。在此机制下,就地初始化将呈现为:先在堆上分配空间,再返回 &out 引用。调用方可自由填充该引用,甚至将子部分传递给其他函数。编译器将追踪所有 &out 引用的使用情况,确保其全部被利用后,才允许代码获取指向堆分配的常规 &mut 引用。

不过该方案远不及Ryhl和Lossin的方案成熟。丁后来告诉我,他与Mandry等Kangrejos编译器贡献者当天在会议间隙,实际在探讨该方案如何与Rust编译器内部机制交互。会议结束时他们已初步构思出实现方案,因此更详细的out指针提案可能很快发布。

最终设计将借鉴C++思路,采用一种保证优化的机制:当构造新值后立即将其移动到堆中时,该值将直接在堆上构造。丁对最终提案的细节尚不确定,他建议最佳方案可能是同时实现PinInit提案和外指针提案,并观察两种方法在实践中的表现。

无论最终采用何种方案,曼德瑞关于“Rust for Linux项目推动语言进化”的观点显然是正确的。尽管这些特性尚处早期阶段,但其应用有望显著简化内核内外涉及用户自定义智能指针的代码。

** 更新:** 自本文所述讨论以来,字段投影机制已获得更新。Lossin向LWN透露,现将所有结构体的所有字段均视为结构固定,因此投影Pin操作将始终生成Pin<&mut Field>或类似类型值。

本文文字及图片出自 Upcoming Rust language features for kernel development

共有 210 条评论

  1. 研究Rust轻量级克隆功能的RFC文档[1]时,我花了不少时间才勉强理解其原理。理解后我对这个特性感到兴奋,但很快又意识到:Rust确实是门极其复杂的语言。

    对我这个既没学过C++也没学过Rust的人来说,它仿佛将“真正”现代C++(而非C语言加些新特性)的所有概念与特性,与Haskell的特性和概念进行了融合。

    然而人们似乎乐于用它实现实用功能,说明它必然做对了某些事。所以我迟早会重新尝试真正使用它。

    [1]: https://github.com/joshtriplett/rfcs/blob/use/text/3680-use….

    • 根据我的经验,C++是更复杂的语言。初始化方式多达8种,值类型分5类(如x值等),格式规范不统一,命名规范不统一,五条规则,异常处理,移动赋值运算符必须检查this != other,完美转发,SFINAE,缺乏特质(traits)等特性导致的变通方案等等。掌握语言还需了解必要的规范,才能更安全高效地编写代码(若移动构造函数未声明noexcept,扩展对象向量时会引发复制),同时要学习各种非理想的替代方案,比如错误处理机制。

      • 为何要检查 this != other?我曾在代码库中见过一次,当时就认定这是多余的。

        作为选择放弃上述所有操作、直接在C++里写C代码后生活变得轻松许多的人,我在此提问 😉

        • 考虑将 src 的内容移动赋值给 vectorthis。第一步是 this 释放其资源,第二步是将 src 资源赋值给 this,第三步是将 src 资源设为空。

          srcthis 相等且未进行检查,最终将导致 src/this 资源被销毁,结果会得到一个空向量(因最后一步会清空所有内容)。

          预期行为应为无操作。

          • 你说的是移动操作,但我不知为何联想到交换。

            我可能仍不会在意,除非明确需要向自身移动。试图将所有内容拆解成千片碎片,再分别进行泛化与优化,纯属徒劳。

      • 感谢你帮我梳理了为何连受限的现代C++子集都如此复杂的思路。

      • 我离开这行已久且并不怀念,但原以为现代目标是零规则而非五规则。

      • 确实如此,C++在某些方面过度设计得令人毛骨悚然——更糟的是完全忽视了非资深/出口开发者的用户体验,而在其他方面却又设计不足。

        近十年虽在努力修正,却又不断添加新颖的高级特性,我认为这注定是徒劳的。

        若将Rust所有稳定(或更糟的实验性)特性纳入考量,其实它的复杂度也未必低多少。

        Rust的精妙之处在于:当你不使用某项特性时,几乎永远不必了解它(C++则不然);即使意外触发未知特性,通常不仅语法不可见,即便使用错误也不会导致段错误,更不会因越界写入等操作引发远程代码执行漏洞。

        因此C++要求开发者预先掌握大量知识,但编译器不会强制检查,错误使用可能导致灾难性后果。

        而Rust允许你逐步学习(大部分内容)。

        它并非完美无缺——当你进入unsafe模式时,便会触及诸多“现代编译器”的复杂性,这些特性如今也需映射到Rust的保障机制中(不过在Rust中拥有更完善的保护措施/可见性)。

        此外,异步编程与循序渐进的学习方式并不十分契合。

        但根据我的个人经验,若团队并非全由资深C++开发者组成,我建议远离C++。另一方面,只要有正确的指导原则/限制条件,让初级工程师使用Rust完全可行(关键在于避免在错误方向上纠结或过度复杂化)。

    • 需注意社区对该提案态度已趋冷淡,其现行版本很可能不会被采纳——恰恰因为 它过于复杂。(https://rust-lang.github.io/rust-project-goals/2025h2/ergono…)

      复杂性未必会自动成为Rust语言提案的致命缺陷——许多Rust特性之所以复杂,是因为它们解决的问题无法用简单方案实现——但这个特定特性的目的,是让编写使用引用计数指针的代码更轻松,并减少程序员需要追踪的内容。它不会让你做些原本无法实现的事,只是让操作更流畅。因此若人们难以理解其功能,那便是弊端所在。

      • 顺带一提,这或许只是我的个人观点,但我区分“复杂”与“繁琐”。某些事物具有本质上的复杂性,即便剥离所有冗余,其核心仍包含众多动态组件——这正是其本质特征。然而,你也可以在事物上叠加额外复杂性,使其功能实现过程变得不必要地繁琐。想想制表师眼中的“复杂功能”——给手表加个显示上次看牙医后经过几周的表盘或许很酷,但这并非腕表正常运作的必需功能,反而让整个装置变得更娇贵脆弱。

        复杂性本身无妨。有时我们面对的正是缺乏简单直接解决方案的复杂问题。但我确实会尽量避免不必要的复杂化。

        • 确实,我之前表述稍显草率。但这种区分在此层面也未必有效——人们常为“特定复杂性是否必要”争论不休。例如,许多人认为Rust的异步机制是多余的复杂性——要么因为手写状态机就能实现(这严重损害表达力,但你真的需要表达力吗?),要么因为每个任务都能创建操作系统线程(这严重损害性能,但你真的需要性能吗?)。而支持异步的观点是:没错,它确实复杂,但更简单的方案无法实现其全部功能。

          • 哦,我明白你的意思了。这完全说得通,你说得对:我眼中的复杂解决方案可能是别人眼中的不必要复杂,反之亦然。我并非要与你争论,更无意纠正你——毕竟并非所有人都认同我对这些概念的区分。

            我的本意更像是“既然聊到这个,顺便说说我对相关概念的看法”。

        • > 简单胜于复杂。 > > 复杂胜于繁琐。

          https://peps.python.org/pep-0020/

        • > 复杂性本身无妨。有时我们面对的复杂问题本就没有简单直接的正确解法。

          最佳策略往往是先解决更简单的问题 🙂

      • 不知是否考虑过块级 clone(var_a, var_b) 方案?虽略显冗长但非常明确,远优于当前方案。

    • 我发现Rust这种语言,远观时令人望而生畏,但一旦动手实践就会很快感到得心应手。

      典型例子就是我初次接触生命周期时完全摸不着头脑。于是直接用 Rc<> 包裹所有类型,照样写出能完美运行的程序。等掌握基础后再学习生命周期,反而轻松多了。大多数人甚至根本不需要关心轻量级克隆。

      • > 典型例子:初次接触生命周期时我完全摸不着头脑,于是直接用 Rc<> 包裹所有类型,最终写出了完全可运行的程序。

        好奇问一句:你遇到过生命周期问题吗?还是说读完相关文档后就习惯性用 Rc<> 包裹一切?其实用 Rc<> 包裹并非坏事,这在浏览器中实现 WASM 时本就是必要操作。

        我至今仍觉得困惑,因为你并非在设定生命周期,只是给它命名以便编译器推导。

        就像声明'static并不意味着静态,它本意是“存活至程序结束”,但你完全可以创建'static对象并在程序退出前释放它们。

        • 我认为将生命周期理解为约束最为恰当。

          例如x: Foo<'a>表示“x受生命周期'a'约束”,即“仅当'a'处于'存活'状态时,才能保证x的安全使用”。

          其隐含前提是“你只能使用那些被保证安全可用的对象”。

          而“移动”操作与生命周期本质不同(即若将x移出作用域,你将彻底失去该对象;而x的'a约束则限制了你“拥有”它的有效范围)。

          因此'static基本等同于“无约束”/“不受限制”。

          因此,x: X + 'static 表示 x 没有任何限制,只要你拥有它就能使用它。

          x: X+'a(或X<'a>)则表示你最多只能在'a'存活期间拥有x。(这并不意味着X背后的数据寿命更短,只是在特定位置,你最多只能在'a'存活期间“拥有”它)。

          因此考察&'static A时,我们得到一种类型:1) 始终通过复制而非移动操作获取,故在可持有期间持续持有;2) 永远可持有。由此推论其生命周期必须覆盖整个程序执行。这并非因为'static限定了“程序结束前”,而是因为无约束的&引用要保持类型安全,就必须存在至程序终止!!

        • 时隔太久记忆模糊,但确信是生命周期传播搞垮了我…一旦某个结构体拥有生命周期,所有使用它的结构体都需拥有,导致多重生命周期并存。而整合这些生命周期(并建立它们之间的关联规则)简直让我脑子快炸了。Rc<>让我巧妙避开了所有这些麻烦。

          • 没错,结构体中的生命周期真是麻烦精。不过在我看来,只有在不使用时性能损失过大的特定场景下,使用它们才是“正确选择”。我理解serde为何采用这种做法,但请注意这些设计基本不会暴露给最终用户。

          • 没错,一旦在结构体里写存活期就容易出问题。通常只该给那些存活期不复杂的结构体定义存活期(比如若预期它会被分配到堆上,那存活期就太复杂了)。

          • 每当需要在结构体中显式指定生命周期时,我都会使用Rc或其他容器。多年来我随意使用Rust编程,每次试图通过生命周期取悦编译器时,都意识到这不值得花时间。若有能让我理解何时及为何需要这样做的通俗易懂资源,我将不胜感激。

            我认为这类问题本该由额外的运行时代码或更智能的编译器处理。生命周期管理在微控制器或超优化场景或许重要,但当我在Apple Silicon上运行普通应用时完全无关紧要。然而这门语言却让简单的GC变得极其痛苦。我热爱这门语言,但并不需要所有这些性能优势。我宁愿承受1%的性能损失来增加引用计数。

            • 你在苹果硅平台上开发,这恰恰是Swift的首选平台。它完美实现了你对ARC的需求,同时保证内存安全。

            • 我们有生产环境系统,约1万行未使用生命周期的Rust代码。有时确实可以不用它。

      • 观察得很到位。我觉得Rust入门文档就该从这点开始:暂时抛开生命周期,需要时直接用Rc<>。

    • 它确实比C复杂得多。但远比C++简单,你不会陷入翻阅参考手册或标准文档的困境来理解自己写的代码。它精准把握了“复杂度恰到好处——不会让你抓狂,又不会让代码变得臃肿”的黄金平衡点,同时将正确性作为默认状态。

    • 除了复杂性不会导致误用(这在C++中是小事),我仍觉得它更易上手。更何况,即使你不知道轻量级克隆机制,使用clippy时它也会建议更符合惯例的构造替换方案。因此即使你未曾接触轻量级克隆,当代码库支持该特性时仍会获得应用建议(通过–fix参数让Clippy自动修复即可轻松实现绝大多数场景)。这显然不是C++的超能力,也解释了为何C++的复杂度持续累积——要么新特性无人问津,要么被以过度复杂的方式使用。

      • > 这绝非C++的超能力所在,正是其复杂性不断累积的原因

        你对实际C++开发经验似乎有限?市面上静态分析工具层出不穷,任何专业的IDE都配备了辅助与自动化的重构工具,能在编写时实时提供修复建议。Rust的clippy并未创造任何新事物,尽管该工具确实优秀…

        • 我在Coverity工作时,客户曾支付天价费用获取如今rustc和clippy免费提供的建议。我对此深表赞赏。

    • > 审阅Rust轻量级克隆功能的RFC提案

      虽然Use特质(建议采用更佳名称[1])颇具实用性,但我尚未看出.use语法相较于.clone()有何额外价值?若编译器能执行这些优化,同样能通过静态分析提示用户移除冗余的.clone()或将x.clone()改为&x。个人认为这比编译器施展黑魔法更合理。

      [1]: https://smallcultfollowing.com/babysteps/blog/2025/10/07/the

    • 虽然它确实是门复杂的语言,但我认为整体而言远比C++简单得多。

    • 作为浅尝辄止的实践者,我认为Rust理论复杂但实践并不繁琐。我在其他讨论中提到过,大型语言模型能提供建议辅助,可先用次优代码起步,随着信心增强逐步优化。

      显然我还没用它开发过生产级项目,但它确实帮我用Python实现的几个应用程序提升了效率。

    • 当前关于“更简易的轻量级克隆”的讨论非常热烈,不仅限于RFC提案本身,而是普遍存在。

      从我所见的所有讨论来看,这个RFC是前景最不乐观的提案之一,因为它以令人困惑且模棱两可的方式混淆了“在将内容移入闭包作用域前隐式执行操作”与“轻量级克隆”这两个概念。

      因此我不期待它以当前形式被采纳/实现,但类似方案可能会出现。

      例如“轻量级克隆”这个名称本身就很糟糕,且无需新语法——若为节省4个字母就把.clone()简化为.use,那我们就是在做错事。同样地,若为实现特定优化而争论,而非通过改进通用优化达成相同效果,在我看来也是本末倒置。

      至于作用域/闭包机制(即 Rust 版 C++ 闭包 `[]` 结构,例如 [&x](){…}),此方案同样欠妥。首先该操作关联的是闭包作用域而非内部调用,将其绑定至内部调用实非良策。尤其当x的副本在多处传递时,这种设计会引发大量歧义——而RFC中并未给出任何示例说明。其次,该概念不仅限于“低成本副本”,有时同样模式适用于高成本副本(尽管主要影响低成本副本)。最后,若真要引入捕获定义机制,为何不直接允许显式定义捕获?

      当然,若存在更紧凑的方案来处理“非复制但仍低成本”的值共享(如句柄/智能指针的克隆),或许允许其在闭包捕获范围外隐式发生(而非仅限调用范围)也说得通。但我认为这属于尚未存在的句柄/智能指针易用性改进的延伸,应在该改进完成后再行考虑。

      (是的,我明白他们使用use是因为它已是关键字,但对句柄/智能指针进行非零成本复制并非真正“使用”,更像是“共享” :/)

    • 顺便提一句,C++在闭包中移动/引用对象时存在完全相同的问题,详见https://en.cppreference.com/w/cpp/language/lambda.html中的“Lambda捕获”章节。

    • 在我看来,这不仅关乎C++与Rust的复杂度差异,更在于这种复杂性(或其他因素)会多频繁地带来不愉快的意外,以及这些意外的后果有多严重。

    • Rust的严谨性令人信服。这才是正道。

    • C++复杂性的症结在于:你必须全盘记住所有规则,一旦遗漏某些细节…嘭!未定义行为和难以捉摸的运行时错误便会炸裂!

      Rust的复杂度确实处于同等量级,但 你不必记住所有细节。通常若因遗忘复杂规则而犯错,编译器会直接提示。

      这在Rust的unsafe模式下不成立,但你很少需要用到unsafe。除了FFI接口,我写了多年Rust代码至今从未用过它。

      异步Rust或许最接近C++的逻辑:“你没在无void返回的promise上下文中显式使用co_return?好吧继续写,但我偶尔会崩溃!可能只在发布模式下,或特定操作系统上发生”。

    • Rust确实比C复杂,但作为同时专业使用过这两种语言的人,它的复杂度远不及C++。事实上,Rust的复杂度更接近C而非C++。

      • 或许你能为一位精通C语言却回避C++(且至今极力回避Rust)的开发者指点迷津:若将C语言的复杂度设为1,那么C++和Rust又该如何定位?此处“复杂度”指的是能否将整门语言同时装进脑海。C语言虽存在复杂的角落,但总体而言并不算过度复杂。(想必你已察觉,我偏爱汇编语言,因其最为简洁。你可以自行构建抽象层,这才是汇编的正确使用方式)。感谢任何见解;若遇更优方案,我绝不固守己见。

        • 若C语言复杂度为1,C++为100,我认为Rust约为25。

          若你钟爱汇编语言,觉得C语言都过于高级,那么你很可能也会排斥Rust(以及Java、Python等所有现代语言)。

    • 我认为Rust本身并不复杂,复杂的是你的实现方式。只是用C/C++实现时,它会让你产生虚假的安全感。

      C++极其复杂——仅初始化机制就有278页专著详述[1]。

      我见过各种编译器放任编写的糟糕多线程代码。在Rust中实现这些会困难得多,但Rust会强制你写出正确的代码。例如我见过用于消息传递的锁池,锁在某个线程被加锁,却在另一个线程被解锁。这会导致频繁死锁,结果每隔半秒左右就有独立进程强制释放所有锁。

      [1]: https://leanpub.com/cppinitbook

    • > 然而人们似乎乐于用它开发实用工具,说明它必然做对了某些事。

      我真的不是在评论Rust!但不得不指出这句话逻辑跳跃。PHP和Visual Basic甚至JavaScript与Go都做对了某些事;但那些主动选择使用这些糟粕的开发者,确实值得被指指点点取笑一番。

  2. > Linux版Rust项目对Rust发展大有裨益

    我刚决定在内核代码库里来个经典的'find -name “*.rs”'搜索,想搞清楚这到底是怎么回事。据我观察,项目仅包含一个API兼容层(位于/rust目录),以及零星的概念验证驱动程序——这些驱动似乎只是现有驱动的简单重写(除未完成的nvidia驱动外),甚至尚未投入实际使用。连Android Binder的Rust重写版本都显得可有可无。

    整个项目看似有趣,但这种编程语言协同开发的实验,难道不该在比全球最重要软件的源代码树更合适的地方进行吗?

    Redox这个实验性操作系统相当酷炫,或许就是未来的操作系统形态,为何不在那里实现呢?

    • 苹果硅的GPU驱动采用Rust编写,作者表示若用C语言实现将困难得多。目前尚未合并至上游。

      “”"通常情况下,当编写如此复杂的全新内核驱动时,从简单演示程序到支持多应用并发调用GPU的完整桌面环境,往往会触发各种竞态条件、内存泄漏、释放后使用问题等各类故障。

      但这次完全……没有发生!我仅需修复几个逻辑错误和内存管理核心代码中的一个问题,其余部分便稳定运行!Rust确实神奇!其安全特性确保只要少数不安全代码段无误,驱动设计必然具备线程安全与内存安全。它不仅引导你实现安全设计,更助你达成优质设计。“”"

      https://asahilinux.org/2022/11/tales-of-the-m1-gpu/

      > 整个项目看起来挺有意思,但这种编程语言协同开发的实验,难道不该在除全球最重要软件源代码树之外的地方进行吗?

      托瓦兹似乎不认同你的观点。

      • 我正想说,既然这是在M1/M2 Mac上运行GPU的唯一途径,确实难以反驳。

      • Rust和线程安全、竞争条件有什么关系?Rust会自动同步共享内存访问吗?

        说真的,他们肯定指的是数据竞争吧?如果是这样,为什么不能用C++原子操作实现相同效果?

        • > Rust与线程安全有何关联[…]?

          Rust 本质上实现了这一理念。请阅读关于 Rust 的 “Send” 和 “Sync” 标记特性的说明。例如:https://doc.rust-lang.org/std/marker/trait.Send.html

          > Rust 会自动为我同步共享内存访问吗?

          远不止于此。(安全的)Rust 会直接报错阻止你编写那些未同步的荒谬代码——省去了生产环境中数据损毁、耗费半年复现调试错误的环节…

          • 这些不就是注解吗?正确使用互斥锁和锁定顺序其实不难,只需保持纪律性和一致性。

            说实话,我记不清上次遇到数据竞争是什么时候了。

            真正的疑问在于:当团队协作时,当并非所有人严格遵循单一理念时,这些机制能否依然有效?

            • > 这些不就是注解吗?正确使用互斥锁和锁定顺序其实不难,只需保持纪律性和一致性。

              空间内存安全很简单,数组索引前检查边界即可。时间内存安全也很简单,使用完毕后释放内存,既不过早也不过晚。正如你所说,线程安全其实很简单。

              但大量实证——源自软件的普遍故障——表明实践中 绝非 易事。尤其在大型代码库中,要记住维持内存安全和线程安全所需的各种复杂条件实属不易。我写过大量引发问题的代码,比如“哎呀,我忘了考虑有人可能用这个通知立刻要求我关机”。

              这些注解的作用在于:当你无意中搞砸某件事时,编译器会像敲醒你一样提醒你——就像你搞错类型或名称时编译器敲醒你那样。根据我的经验,许多人在使用借用检查器时都会经历一个抱怨阶段,抱怨它不正确,但后来发现它其实是正确的,而他们认为安全的模式其实并不安全。

            • 在内核这类庞大且深度耦合的代码库中,正确运用锁定顺序相当困难。

              Rust在此实现了实质性改进,例如Fuchsia团队通过编译时强制锁定顺序的示例[0]所示。C++技术上也能实现(参见Alon Wolf的元编程),但操作起来堪称玄学。

              [0] https://lwn.net/Articles/995814/

              • C++中的借用检查器、生命周期与析构函数参数 (2024)

                https://a10nw01f.github.io/post/advanced_compile_time_valida

                • 其实现的生命周期属于早期Rust中现已废弃的词法生命周期。现代Rust采用非词法生命周期,可支持更多合法程序。Polonius项目将进一步扩展合法程序范围,突破词法与非词法生命周期的限制。此外,他们实现的“借用检查器”实为RefCell,这根本不是Rust的借用检查机制,而是用于在运行时进行有限单线程借用检查的逃生舱(若在多线程中使用,该库不会察觉,但Rust会阻止你这样做)。

                  鉴于委员会的工作方式及其坚持的方向,C++永远不可能成为安全的语言。

                • > 带状态的元编程

                  有趣的是,正如某篇链接文章所述,C++委员会似乎并不推崇带状态的元编程,因此其地位尚不明朗。核心工作组议题2118指出:

                  > 在模板中定义友元函数,随后引用该函数可实现元编程状态的捕获与检索。此技术晦涩难懂,应判定为非法形式。

                  > 2015年5月会议纪要:

                  > CWG一致认为此类技术应判定为非法形式,但具体禁止机制尚未确定。

                  [0]: https://cplusplus.github.io/CWG/issues/2118.html

            • > 这不就是注解吗?

              “仅仅是”注解…但它们(绝大多数情况下)由编译器自动添加并强制执行。

              > 正确使用互斥锁和锁定顺序并不困难,只需保持纪律性和一致性。

              是的,就像避免类型混淆/越界访问/释放后使用等错误“只需要[s]一点纪律性和一致性”那样简单?

              将这类工作交给编译器/语言处理的意义,恰恰在于当你的纪律性与一致性出现松懈时——尤其在处理大型/复杂系统或团队协作时——总有东西能为你保驾护航。毕竟我们终究只是凡人。

              当团队协作时,一切是否仍能完美契合?毕竟并非所有情况都严格遵循单一特定理念。

              关键在于,发送/同步操作几乎总是由编译器自动处理,因此团队协作和编程哲学从根本上就不构成问题。不妨将其视作常规强静态类型系统检查(例如禁止将类型A对象传递给要求类型B对象的函数)在跨线程场景中的延伸。

            • > 这不就是注解吗?正确使用互斥锁和锁定顺序并不难,只需保持纪律性和一致性即可。

              并非如此。由于Rust中的互斥锁是容器类型,你也不需要过多关注锁定顺序。调用锁定方法时,只能通过引用获取内部值。

              • > 由于Rust中的互斥锁是容器类型,你其实不必过度依赖锁定顺序。调用lock方法时,你只能以引用形式获取内部值。

                作为容器的互斥锁与锁定顺序问题(死锁)无关。

            • > 正确使用互斥锁和锁定顺序并不困难,只需保持纪律性和一致性即可。

              哇,你真是聪明绝顶!看来你根本不需要Rust。但对我们这些觉得并发编程困难的人来说,它很有用。

        • > Rust和线程安全、竞争条件有什么关系?Rust会自动同步共享内存访问吗?

          Rust严格的所有权模型能更正确地处理跨线程共享或传递的数据。

          > 说真的,他们肯定指的是数据竞争吧?如果是这样,为什么不能用C++原子操作实现相同效果?

          Linux内核不使用C++。

          若开发者和后续维护者能谨慎处理所有细节且零失误,C++或C语言同样能编写安全代码。Rust的优势在于编译器在语言层面强制执行安全机制,无需依赖代码修改者避免错误或违规操作。

        • Rust的设计彻底消除了数据竞争,也让编写线程安全代码变得更容易。虽然仍存在竞争条件,但相较于C++已大幅减少(至少我认为如此)。

          这并不妨碍你编写正确的C++代码。Rust在功能上(可实现的程序类型)严格弱于C++。C++的问题在于:最简单的实现方式往往也是最错误的方式。你可能甚至意识不到变量在多线程间共享时需要原子操作。

        • > Rust与线程安全及竞争条件有何关联?它会自动同步共享内存访问吗?

          实际上非常接近!Rust会静态阻止你在不同线程中并发访问同一数据,除非使用锁或原子操作。

          > 为何不能用C++原子操作实现相同效果?

          你可能会忘记?

          • Rust还会阻止你在线程间共享不可共享的数据。

          • 第一个评论描述的是数据竞争的定义,而非防止竞争条件。数据竞争其实很常见(当然C++没有静态预防机制)

            • > 数据竞争很常见

              想象这段C++代码:

                class Foo {
                // ...
                public:
                  void frobFoo();
                };
              

              现在,是否可以同时从多个线程调用frobFoo?可能可以,也可能不行——如果未在文档中说明(或你不信任文档),就必须阅读完整实现才能确定。

              现在想象这段Rust代码:

                struct Foo {
                // ...
                }
              
                impl Foo {
                  fn frobFoo(&mut self) {
                    // ...
                  }
                }
              

              那么frobFoo能否被多个线程同时调用?不行,语言会自动阻止这种操作。

              若将&mut self替换为&self,则可能允许调用。此时可通过纯粹的局部推理(仅考察Foo实现的特性,而非具体实现)判断是否安全,若 不安全,语言仍会自动阻止该操作(同时禁止函数执行任何可能导致不安全的行为)。

      • Rust语言并非旨在解决竞态条件。在我看来,这更像是任何语言中都可能出现的过度“谨慎”且低效的代码。就像在C++中对所有操作都使用std::shared_ptr那样……?

        • 该评论可能指代内存访问引发的数据竞争,这类问题可通过使用SendSync特性避免,而非泛指更普遍的竞态条件。

          • 明白了,但这并非我评论的核心。我不熟悉rustlang,若有人能将rust特有的术语转化为通用表述,或许我才能回应这个问题。

            • 我不确定是否完全理解你评论的要点。

              Rust确实成功保证了数据竞争的缺失。它还保证了通常由竞争条件引发的内存不安全性的缺失(公平地说,这主要就是指“保证数据竞争缺失”,不过也包括诸如“竞争条件不会导致释放后使用或越界内存访问”等情况)。

              若你所指的“说明”是“展示C/C++如何实现”——它们确实做不到,这已是公认事实。

              若你所指的“说明”是“证明Rust未能兑现其承诺”——这无异于邀请他人用一个HN评论的篇幅,向你详尽剖析Rust底层机制的每个细节。与其指望别人在HN上信手重现这些材料,不如自己上网查找相关资料阅读。

              • 我的观点是:根据经验,那些写得拙劣、过度谨慎的代码往往以牺牲可维护性或性能为代价换取安全性。

                遗憾的是我不懂rustlang,无法判断其特性无法用更常见术语描述是源于编写者的能力不足,还是这些特性与本讨论无关(参见帖子标题)。

                • 关键在于,你真正探讨的并非Rust的“特性”(如帖子标题所用词汇),除非该特性指代“消除数据竞争”或“内存安全”——我认为这两者都是定义明确的概念†,而Rust确实具备。你实际在询问这些特性的实现方式,答案在于Rust通过贯穿所有功能的统一设计来维持这些特性。

                  若要完整解答你的疑问,我几乎需要向你阐述Rust的大部分设计原理。需要阐述特质机制、自动特质、不安全特质实现、所有权系统、借用检查器,以及为实现内部可变性而设计的实用方案。随后才能指向标准库中前述的Send与Sync概念——此时这些概念才真正具有意义。最后通过实例展示所有机制如何协同作用,最终实现内存安全、高效且符合人体工程学的多线程原语。

                  但这已不再是关于Rust语言特性的讨论,而是Rust语言的通识教程。因为要真正理解这些构建安全抽象的原语如何运作,你需要掌握Rust的大部分知识。

                  Send和Sync(如前文所述)虽是实用的搜索关键词,但在合理的Rust学习路径中属于进阶内容,而非入门首选。对于已掌握Rust基础却未接触过这些特性(或线程)的开发者,我能快速讲解其原理——因为当你理解“Rust其他机制如何运作”后,这些概念便显得简单明了。跳过基础知识的讲解毫无意义。

                  † 诚然“内存安全”概念可能由Rust推广,但其本质等同于“消除未定义行为”——任何C语言程序员都应理解这个概念。

                • > 我的观点是:根据经验,拙劣编写的过度谨慎代码往往以牺牲可维护性或性能为代价换取安全性

                  没错,但这恰恰是Rust的价值所在:你无需使用这些过度谨慎的防御性构造(至少无需为防止数据竞争而使用),因为语言会自动为你阻止这些问题。

              • 安全的Rust确实如此。至于封装内核API的Rust接口能否为使用它们的驱动程序提供安全性,仍有待观察。我认为它确实能在某种程度上实现安全,但对投入的精力和开销是否值得存疑。个人认为这些资源更应投入其他领域。

            • 问题恰恰在于此——Rust 中的某些概念在其他常用语言中并无对应。在此场景下,Rust的类型系统实现了数据竞争安全:它能在编译时阻止数据竞争,这与C或C++的实现方式截然不同。除非访问经过同步处理(原子操作、锁等),否则它会阻止跨线程对值的可变访问(通过编译时错误)。

              • 据我所见,Rust的可变性也是类型系统构造?即在进行相关检查时,它默认所有其他代码都是Rust代码?

                • > Rust的可变性也是类型系统构造?

                  是的

                  > 即在进行相关检查时,它默认所有其他代码都是Rust代码?

                  不完全是,它仅假设你在编写调用/被其他语言调用的代码时遵守了文档规定的不变量。例如,若我定义了extern “C” fn foo(x: &mut i32),则要求:

                  – x 指向正确对齐且正确分配的 i32 类型(不指向空值,也不指向未分配页面的任意位置)

                  – 在调用foo期间,内存唯一可能被访问的途径是通过x。这意味着系统其他部分不会向x写入数据,也不会对其存储值做出任何假设——直到函数调用返回为止(原则上Rust允许在x的内存中存储临时值,即使代码仅传递x而从未实际操作它,只要foo返回时内存仍包含预期值即可)。只要foo返回时内存包含预期值即可)。需注意这意味着同一内存的指针不能通过其他方式传递给Rust(例如通过未加锁的静态变量)。

                  – foo 将通过标准“C”调用约定调用(例如在 x86_64 Linux 上,这意味着栈指针必须对齐至 2 字节边界。此类约束在汇编中极易违反,而在 C 代码中几乎不可能违反)。

                  程序员需自行验证不变量,正是Rust将FFI代码视为“不安全”的原因——程序员错误可能导致系统不安全。但若程序员确信已维护不变量,仍可获得系统层面的保障。

                  Rust的核心在于局部推理。它并不关心系统其他部分的状态,只要调用方遵循约定契约即可。相较于C语言,Rust对契约的定义更为明确。

                  • 此外(在2024版中)我们可声明FFI函数为安全调用,从而避免编写仅做传递的轻量级安全封装。虽然仍需使用unsafe关键字引入FFI函数名,但通过标记安全属性,实际调用方无需在意该函数是否由Rust编写。

                    这种安全声明范围较窄,例如C函数往往并不真正安全:它们可能要求有效指针参数(这本身不具备安全性),或对参数相对值、系统整体状态有依赖性要求——这些都无法通过Rust验证,仍属不安全范畴。但对于特定场景,这种机制仍能带来显著改进。

                    • 此外“安全”与“不安全”具有非常特定的含义,不同于更广泛的用法。调用编写良好的不安全代码本身并不危险,它本质上更关乎行为责任的归属——是编写者还是编译器承担责任。

                      我更喜欢“检查型”和“非检查型”的术语,但不足以推动实际变更,作为专业术语它们完全适用。

                • 确实如此。正如C++中的“const”是类型系统构造,它默认所有其他代码都是C++(或至少通过不随意修改字节来配合C++代码运行)。

                  据我所知,任何语言提供的任何保证都只是“语言构造”,一旦存在行为异常的其他代码执行,这些保证就会失效。

            • 数据竞争是竞争条件的一种特殊形式;这并非Rust专有术语,但因其独特性在Rust讨论中频繁出现——这正是其价值所在。

              • 我指的是trait send的同步机制。本以为这是常识,毕竟Rust并非唯一易受数据竞争影响的语言。

                • 毕竟易受数据竞争影响的语言不止Rust一种。

                  关键恰恰在于它并非如此。“特质send sync相关机制”明确规定了该类型值能否在线程边界间进行移动或借用操作。

            • 我的意思是,可靠地追踪所有权,从而确保诸如别名写入必须在读取前完成,这难道不是很有帮助吗?

              虽然无法杜绝所有竞争条件,但至少能避免部分错误。并发编程本就令人头疼,任何机器可验证的保障对从业者而言都值得拥有——当然,我本人并非此类从业者。

            • 根本不存在叫“rustlang”的编程语言。它就叫rust。

        • 嘿。这话太C++了:“我想做正确的事,但代码就变慢了。”我懂,我以前用C++写过游戏。所以感同身受。

          我只能劝你:敞开心扉。Rust是昙花一现的潮流吗?只是业余爱好者追捧的炫酷新玩意儿?还是颠覆性的存在?去深入探索Rust吧。把它编译成汇编代码看看生成什么。被借用检查规则折磨到顿悟。写些不安全代码体会“不安全”的真谛。形成自己的判断。

      • > Torvalds似乎不认同你的观点。

        我向来厌恶这种不加思考的权威诉求。要么提出自己的论据并捍卫,要么就别费这个劲。

        这个GPU驱动看起来相当不错。朝日树中的Rust兼容层似乎还有更多内容,他们能这么快就发布出来确实很厉害。我很好奇内核级Rust和用户空间Rust在代码膨胀方面有何差异(个人认为用户空间Rust在这方面表现很糟糕)。

        • 这是在盲目诉诸权威吗?我认为这种谬误的运作机制并非如此。关键在于权威本身似乎与你观点相左——前提是我们认同托瓦兹仍深谙其道。他未与你持相同怀疑态度本身就是有力论据。重点在于:与其执着于我们遥远的感受,不如暂且停下脚步,更深入探究为何更接近核心的人士会持不同见解。为何要更重视Reddit上无名之辈的意见?

        • > 我向来厌恶盲目诉诸权威。要么提出自己的论据并加以论证,要么就别费这个劲。

          你先前还强调Linux作为全球最大最重要的源代码库,转头却选择无视该项目负责人的意见。

        • 公平地说,赋予Linux那位倾听众多顶尖维护者意见的BDFL(首席开发者)一定公信力,并非盲从。

          除非你提出可证伪的具体主张需要辩护或反驳,否则默认专家意见具有隐含正确性绝非谬误。这只是明智之举,即便对你当前的辩论无益。

        • 提及权威观点未必等同于“诉诸权威”。此处他们只是试图提供视角,而非将托瓦兹的意见奉为神谕。

        • > 我向来厌恶盲目诉诸权威的行为。

          但此处援引的并非普通权威,而是定义Linux并以其命名的 终极权威

          • [已删除]

            • 你连好奇心都没有,只因读过维基百科关于逻辑谬误的条目,就否定Linux创始人兼数十年的知识守护者与你持相反观点,这实在太懒惰了。

            • 更何况这位可是创造了“世界上最重要的软件”的人,正如你所言。针对你质疑的具体问题,引用该领域的权威观点才是最具说服力的论据。

              • 咦?

                引用权威观点时,难道不该说明其持此立场的理由吗?光提权威本身未免太草率…

                他谈论Rust的场合多的是。直接链接他的相关评论就行,就像引用ashai linux那样。

                • > 引用权威观点时,说明其持此立场的理由总比单纯引用权威本身更有说服力

                  为什么?当你质疑权威观点时,需要让听众更关注你的论证过程——证明权威为何错误。当你只是抛出遥远且可能信息不足的观点,却毫无论据支撑时,不该指望别人替你做功课来证明你错了。只有当人们几乎不考虑你的论点,而只关注对方地位时,诉诸权威才算真正的谬误。

        • 所以比起引用最权威的专家意见,你宁愿采信拙劣的改写和键盘专家?你自己也“做独立研究”吗?

    • >据我所知,连Android Binder的Rust重写版都只是残留产物。

      此说法有误。Android Binder的Rust重写计划旨在完全取代当前的C语言实现。

      https://www.phoronix.com/news/Rust-Binder-For-Linux-6.18

      而且为苹果M系列硬件编写的大多数核心驱动都是用Rust实现的,这些绝非简单的重写或概念验证。

    • 要实现复杂驱动程序,必须先构建驱动程序的基础接口层。RfL项目正致力于此,持续向上游贡献基础设施工作,直至积累足够成果提交复杂驱动程序。红帽公司正在开发nova驱动,朝日公司负责苹果GPU驱动,Collabora则致力于ARM Mali驱动。若三款GPU驱动都不算复杂的真实驱动程序,那什么才算?

    • > 就我所知,连Android Binder的Rust重写版本都只是残留产物。

      残留体现在哪里?Linus代码库中的版本提交信息(来自提交 eafedbc7c050c44744fbdf80bdf3315e860b7513 “rust_binder: add Rust Binder driver”)显示其相当完整:

      > 该Rust绑定驱动通过了Android开源项目中所有验证Binder正确性的测试。我们已成功在设备上启动系统,并无障碍运行各类应用与功能。测试环境涵盖Cuttlefish安卓模拟器设备及Pixel 6 Pro真机。

      > 关于功能对等性,Rust绑定库目前已实现C绑定库支持的所有功能,仅缺少部分调试设施。在将Rust实现提交至上游之前,这些缺失的调试功能将被补全。

      —-

      > 这类编程语言协同开发的实验,难道不该在除全球最重要软件源代码树之外的其他地方进行吗?

      Rust for Linux项目最初确实作为树外项目启动。关键在于:无论树外完成多少工作,若认真考虑集成,终究需要在某个节点引入实验性支持——这正是当前正在发生的情况。

    • 该项目的初衷并非“编程语言协同开发”的实验,而是将Rust应用于Linux内核开发。项目发起者正是渴望使用Rust的Linux内核开发者。起步虽缓慢,但正如你所言,这是全球最重要的软件,因此进展谨慎,力求打牢基础后再全力推进。

      Rust能从项目中获益实属额外收获。

    • “实验”实属误称。Rust问世已久,在编写可靠代码方面展现出充分优势,我们确信这是正确方向。

      • 但内核中究竟哪里能看到这种代码?我认为这正是楼主的核心质疑:实际演示在哪里?

        • 初期进展缓慢实属必然——在开展实质性工作前需完成大量绑定,而绑定/FFI往往是繁琐易错的环节,必须耐心打磨确保无误。这正是处理C与Rust之间阻抗失配的关键环节,需将所有隐式规则(若可行)转化为类型系统表达。

          等所有绑定就绪且开发者积累更多经验后,进展就会加快。我一直期待扩展 bcachefs 对 Rust 的使用,目前它仅在用户空间运行,但我已为 bcachefs 的核心 B 树 API 实现了初步绑定。

          真正的迭代器、闭包、更优的数据类型——当这些特性能取代成页的宏魔术时,体验将焕然一新。

        • Rust内核代码的主要实战案例是Asahi GPU驱动,虽尚未合并至上游,但已采用你所见到的上游接口。

        • 例如已合并至6.18版的绑定驱动程序。它已存在,待准备就绪便会落地。

          • 在类似讨论中,我常感到相关进展的重要性被低估——比如Rust在Android和微软生态中的日益普及。那些认为C语言足够好的人(在我看来)常提出类似观点:只需像John Regehr及其同事提出的“友好C”方案[0]那样,设计一种更少无限定义倾向的C变体。可惜Regehr在一年半后得出结论,这种方案无法通过共识途径实现[1]。但他确实指明了前进方向:“像Android团队这样有影响力的群体,可以创建一种友好C方言,用于构建项目中的C代码(至少是安全敏感的C代码)”。我认为这种趋势正在发生——只不过与其锁定更优的C语言方案,多个重要项目都选择将Rust作为发展方向。

            雪崩已然启动。此时再让小石子投票已为时过晚。

            0: https://blog.regehr.org/archives/1180 1: https://blog.regehr.org/archives/1287

            • 哎呀。这篇读来令人沮丧:

              > 本文长篇大论的结论是:我已丧失推动这项工作的信心。

              绝望的精髓:

              > 另一个例子是当32位整数向右移位32位时该如何处理(这在C和C++中属于未定义行为)。Stephen Canon在推特上指出,许多针对ARM编译的程序若结果非0就会崩溃,而大量针对x86编译的程序在结果偏离原始值时也会失效。

            • 某些资金雄厚且有影响力的行业领域认定这是发展方向。我认为Rust存在与C++相同的弊端:过于复杂,而内存安全的C语言将实用得多。令人遗憾的是,这方面投入的资源远不足够。

              • 我完全不认同存在比Rust更简洁的低级†内存安全C语言的可能性,更遑论其必要性。在我看来,Rust的复杂性本质上源于实现内存安全所需的结构设计——这些设计在确保安全性的同时,又避免了过度增加使用难度††。

                即便如此,我们也没有这样的方案。Linux似乎应该采用现成且经过验证的解决方案,而非尚未开发或未被证实可行且实用的方案。

                内存安全并非Rust的唯一优势,它还提供了更具表达力的类型系统——该系统能预防其他错误(尽管并非绝对有效),并能提升编程效率。假设我们获得了某种规避复杂性的内存安全C语言…我甚至不愿用它替代这种兼具表达力与多重优势的内存安全语言。

                † 内存安全的托管C语言当然可行(参见https://fil-c.org/),但似乎不适用于内核环境。

                †† 虽然存在替代Rust设计选择的方案,但复杂度并未显著降低。另可尝试舍弃异步的复杂性,不过Rust本身就支持视作异步不存在的方式使用——这纯粹是增值特性。类似的例子可能还有一两个,只是此刻未能想起。

                • 我并不认同。首先,Rust并非凭空出现,此前已有更贴近C语言的内存安全C变体。其次,我甚至不认为内存安全重要到足以凌驾于其他考量之上——例如在内核中使用两种语言带来的复杂性(即便忽略Rust本身的复杂度)。当然这并非我能决定,而是谷歌等公司的影响力使然。但我仍认为这是个错误,它凸显的更多是某些科技公司对开源项目的影响力,而非其他因素。

                  • > 首先,Rust并非凭空出现,此前已有更贴近C语言的内存安全变体。

                    能否举例?那种既保持低级语言特性,又具备实用级易用性的方案?

                    > 其次,我甚至不认为内存安全重要到足以凌驾于其他考量之上

                    你在先前评论中提到“内存安全的C语言将更有价值,遗憾的是这方面投入不足”。既然建议人们放弃现有工作转而开发内存安全的C语言,你理应为这个概念辩护,而非退缩到否认内存安全本身具有价值。

                    我无意与你争论内存安全的优劣,参与讨论时本以为你已承认其价值。

                    • > 能举个例子吗?既保持低级语言特性,又具备实用级易用性的语言?

                      他们当然无法举例,因为这样的语言根本不存在。某些人出于种种原因难以承认:(1)Rust并非现有理念的简单组合(其借用检查器具有创新性,据我所知线程安全机制中的Send和Sync等特性在文献中也未曾出现);(2)即便存在理念融合,其中许多核心思想被困在远未成熟的语言中,根本无法投入工业应用。在Rust 1.0发布前后,根本不存在其他真正旨在全面替代C++(而非仅作补充)的替代方案。十多年来,几乎所有开发此类语言的资源都倾注于Rust,这正是它能进入Linux内核而[此处可填你钟爱的语言]未能入选的原因。

                      顺带一提,这也解释了为何开发者更倾向于通过可扩展机制(如泛型字段投影提案)解决Rcu投影这类复杂场景,而非因Rust当前无法优雅处理这些问题就放弃该语言。Rust缺乏替代方案正是推动人们探索这些抽象机制的核心动力。反之,当Linux内核(而非某个业余爱好者)为这些特性请求背书时,它们真正融入语言体系的可能性将大幅提升。

    • 当前内核中Rust代码覆盖范围相对有限,但这绝不能被视为否定Rust在内核中的实用价值——毕竟有核心维护者公开宣称,他们正竭尽全力阻止任何Rust代码合并到内核代码库中。

    • > 这类编程语言协同开发实验,难道不该在除全球最重要软件源代码树之外的其他地方进行吗?

      语言协同开发并非Rust独有。GCC和Clang中也存在大量专为内核使用设计的特性。

    • > 这里仅存在一个API兼容层(位于/rust目录下)

      光是厘清Linux内核API的安全规则究竟是什么,就是一项艰巨任务,更别说将其编码为计算机可读的形式了。

      这段代码并非关于C<->Rust的FFI,而是将Linux内核API特性编码为安全的Rust API。

      调用/排序规则的不确定性正是内核C编写困难的根源。要理解VFS锁定规则,你几乎需要模拟Al Viro的大脑并重现他毕生的经验…

    • > redox是个相当酷的实验性软件,或许是未来的操作系统,为何不在那里实现?

      因为人们希望在使用C语言的地方也能用Rust,对吧?你的批评完全合理,但忽略了人们渴望在日常使用的场景中获得优质体验。而且 既然这是项目负责人想做的事,似乎不存在问题/现实障碍。

    • > ‘find -name “.rs”’

      既然这是Git仓库,我建议用 `git ls-files ‘.rs’`。

    • > 这操作是否该移至其他位置?

      这个想法很有意思。你应该告知这位同事你不同意他的技术决策 -> torvalds@linux-foundation.org

  3. > 自本文所述讨论以来,字段投影相关工作已更新。Lossin来信告知LWN,现所有结构体的所有字段均被视为结构固定,因此投影Pin将始终生成Pin<&mut Field>或类似值。

    咦,我漏看了这部分。虽是技术细节,但很高兴他们做出这个决定,这解决了许多讨论中的障碍。

  4. > 最终设计借鉴C++思路,采用一种保证优化的机制:当构造新值后立即将其移动到堆中时,该值将直接在堆上构造。

    需注意该提案名称存在争议,因“优化”一词易引发误解(暗示其可选性或受后端实现影响)。

    • 我可能低估了问题的复杂性,但通过定义正确的调用约定难道不能解决吗?

      “任何大于x的结构体,或标记为特定类型的结构体,调用方需提供大小正确的缓冲区外引,被调用方直接将结构体写入该缓冲区。”

      这样就能像写普通代码一样:

          fn initialize() -> A { 
          // 初始化代码 
          }
      

      直接生效?还能在其他场景减少冗余复制?考虑到该议题投入的精力,以及现有方案的低效性,我总觉得自己漏掉了关键点。

      • 关键在于这种方案仍会迫使你在许多场景下创建更大类型,例如Result<Large, Error>

        本质问题在于组合性。当你从一系列大型类型构建超大型类型时,若其中任何步骤存在失败可能,常规返回ABI就会迅速失效。

    • 为何不直接引入new操作符,反而要搞什么后台优化?这样对所有人都更清晰明了。

      • 因为构造函数的特性实在特殊。通常在Rust中,结构体构造时已满足所有不变量,因为构造是初始化函数的“最后一步”。但采用C++式构造函数时,初始状态下所有字段都处于无效状态,随后需逐个字段逐步建立不变量。这与Rust的安全承诺存在根本冲突。即便在Java这类安全语言中,若从构造函数调用其他函数,也常会因观察到构造中的实例违反常规不变量而引发错误——这也是Rust希望规避的情况。

        • > 但采用C++式的构造函数时,初始状态是所有字段都处于无效状态的结构体,随后通过逐字段初始化逐步建立结构体的不变性。

          据我理解,这正是MaybeUninit存在的意义。即便解决了MaybeUninit初始化安全性的问题(理论上可通过&out引用解决),实际仍存在缺陷:例如当T类型包含空位区或自由填充区时,MaybeUninit本身却不具备这些特性,因此除特殊情况外无法直接将MaybeUninit“投影”为单个字段。我理解C++的局部初始化在原理上存在完全相同的问题,只是由于代码正确性标准远不如C#严格,这类问题暴露频率较低。

    • C++将此类语义称为省略(elision)。

    • “合并堆构造”或“合并堆分配”如何运作?

  5. 每次提到功能特性,我总忍不住感叹:“玩得开心是好事,直到有人把Tokio塞进内核里”。更妙的是,如果Rust足够完善,有人能实现直接组合渲染器,我们就能让整套应用完全在内核中运行——这可就…有趣了。

    • 在内核中实现异步运行时轻而易举。内核的工作队列本质上就是运行时。

      • 但这不就成了热启动运行时?这会打破Rust关于“未来值在被轮询前不执行任何操作”的假设吧?除非你把任务延迟到轮询调用时才提交到队列里

      • 我差点要为“轻而易举”这个说法生气了。不过看到你的用户名就释然了,哈哈。你当然有资格这么说,感谢你的贡献!

    • 不确定你是否在开玩笑,但若认真说——这完全误解了Rust(及C语言)在内核中的使用方式。

      正如编写内核代码时无法使用C标准库,Rust需通过no_std选项编译。你既不能使用cargo,也无法调用crates库。

      你可能需要重写tokio库的一半内容,用内核级抽象替代套接字等与操作系统交互的组件。

  6. 这些似乎是Rust在Linux平台上首次为语言本身(而非仅限内核领域)带来的特性。在我看来,长期聚焦内核特性开发反而阻碍了语言本体及标准库的其他部分进展。

    • 据我所知,系统编程是Rust的首要应用领域,已有大量项目致力于操作系统、嵌入式系统及其他裸机场景的开发,同时也在推进与复杂C代码库的互操作性。

      这些特性乍看之下相当通用,并非特指内核领域,实则是现实世界中此类编程的重要工具。

      • 这些变更对C语言互操作性有何影响?我最近没关注相关动态。

        • C语言互操作性多年来一直表现优异。目前唯一仍处于不稳定状态的是可变参数函数的定义/暴露功能(其调用支持已于多年前稳定)。实际上,你几乎能在(部分不安全的)Rust中实现所有C语言功能,甚至有c2rust这类项目可自动完成这种转换。

          这些新特性旨在让内核开发者所需的功能在 安全的 Rust中实现。这往往需要支持一些相当复杂的抽象概念,其中部分内容无法用当前稳定版Rust表达。

          • > C语言互操作性多年来一直非常出色。

            这仅适用于主要使用`cargo`并希望从Rust交互C语言的情况。反向支持则远不完善,且`rustc`并未标准化对象生成机制。这正阻碍着`systemd`等项目将Rust引入其开发流程。

            https://github.com/systemd/systemd/pull/19598

            • > 仅当你主要使用 cargo 且希望从 Rust 调用 C 时才适用。

              Rust 的 C 互操作性如何依赖 cargo?

              > 反向操作的支持度则低得多,且 rustc 并未标准化对象生成机制。

              我认为此处的理解是:你将在Rust代码中使用extern “C”和/或#[repr(C)],从而获得纯C接口。尝试从其他语言调用“原始”Rust代码的情况极为罕见,甚至可能根本不存在。

              > 这正积极阻碍着像`systemd`这样的项目将Rust引入其项目体系。

              能否从该讨论中指出具体实例?我快速浏览后并未发现有人明确指出从C语言使用Rust存在问题。

              • 在 systemd 考虑采用 Rust 之前,必须解决 https://github.com/rust-lang/rust/issues/73632 并将其集成到 meson 中。

              • > Rust的C语言互操作性在哪些方面依赖于cargo?

                Rust和cargo是否允许同一程序中不同对象对相同C头文件进行多重解释?这正是C库常用的实现方式——借助预处理器技巧实现,尽管我认为这种做法本不该成为常态。

                • Rust和Cargo本身不构建C程序,因此严格来说答案是“不支持”。

                  但部分开发者会利用Cargo的构建脚本编译C程序,这些程序可链接至Rust程序中。具体支持程度取决于脚本的实现方式——根据我的经验,这类脚本通常会委托给项目使用的构建系统。因此理论上应该能正常工作。

                • 我认为确实如此。Rust和Cargo根本不会直接使用C头文件,它们消耗的是由bindgen生成的绑定文件(或手动编写的绑定文件)。因此,如果你需要对C头文件进行多种解释,很可能可以生成多个绑定文件。

                  如果头文件被C代码使用,而该C代码又被Rust使用,那么你将获得C语言的完整支持,因为它将由C编译器进行编译。

          • 对此我稍有异议。从 Rust 调用 C 函数体验极佳,反之则不然。通常需要手动创建类型,将Rust集合拆解为C兼容结构(例如将Vec分解为ptr、len、capacity),并确保跨语言传递的内存使用正确分配器释放。即便cbindgen能处理繁琐的转换,仍需精心设计跨语言API。

            我目前正在参与一个相当复杂的C与Rust嵌入式系统项目,要让跨语言接口稳定且无内存泄漏,耗费了大量精力。更棘手的是,该平台无法使用valgrind或gdb调试工具。

            • 我认为这可能取决于人们理解“互操作性”时设定的范围 。我认为可以合理地同时指出:互操作的“技术实现”非常出色——既能创建行为与C库无异的Rust库,又能让几乎所有C库被Rust代码调用;但互操作的“使用体验”尚有不足——正如你所言,实际编写粘合代码的过程确实颇具挑战。

              • 我认为这个评价很公允。正如你所说,cbindgen让整个过程的机制变得毫无痛感,链接操作也极其简单。这点尤其值得称道,特别是与其他语言相比。

          • 明白了,感谢说明

        • 从Rust调用C代码?相当不错。

          编写可被C调用的Rust代码(但需在同一应用内)?可行但略显棘手。

          编写模拟C共享库的Rust代码?相当痛苦,且缺少关键特性(最明显的是缺乏完善的符号版本支持)。若愿意妥协,理论上仍可实现。

          FFI安全机制中还存在些微妙且易出错的细节:

            * #[repr(C)] 枚举仍遵循 Rust 枚举规则,C 调用方极易触发未定义行为,需使用 open_enum 等方案。所幸 cbindgen 编译器不够“聪明”,无法识别 #[open_enum] 是过程宏,因此会生成非枚举类型。
            * 在Rust 1.63引入io_safety之前,从C处理文件描述符时避免意外关闭操作堪称噩梦(尽管这是Rust更广泛的问题)。BorrowedFd相当实用——但需注意Rustix会因负数文件描述符引发panic,实际应用中需添加验证机制并自定义类型。而#[repr(transparent)]对此提供了绝佳解决方案。
            * 使用 C FFI 进行非基础操作时,必须深入研读关于 Rust 不安全模式的文档。
            * 需借助大量编译器内部机制、构建脚本及其他技巧才能获得预期输出。
            * cargo-c 和 cbindgen 等工具虽优秀,可能适用于 80% 的项目,但剩余 20% 的场景因缺乏有效工具而举步维艰。我尚未尝试直接使用rustc解决剩余问题,但预计会更加痛苦。
          

          我认为Rust与C的交互机制相当出色,但仍有巨大改进空间。核心功能实现后,似乎很少有资源投入到后续优化中。

          来源:我正在编写一个主要通过C语言FFI调用的Rust库,遇到了大量问题…

          • 感谢分享,我一直想在需要大量Rust<->C双向FFI调用的项目中使用Rust。听起来还是有点棘手。

        • 问题出在C++上,C语言多年来一直很简单

          • 内核相关的变更是否适用于C++互操作?老实说我不清楚。

            • 内核本身不使用C++,因此仅限于偶然涉及。

              我过去参与的某些C++/Rust互操作项目本可受益于自定义类型特性,但并非因C++部分而特别需要。事实上,若纯粹是Rust项目同样能充分利用该特性… 嗯…姑且当作参考吧。

    • 内核/固件开发领域其实新增了大量特性,只是未被大众关注。

      Philopp 在此领域尤为推动变革。

    • 我认为Rust在底层系统编程领域有其定位,而用户空间的其他场景更适合采用编译型托管语言——诸如Self、Inferno或Android这类架构的系统便是明证。因此我认为这些致力于实现C语言级底层能力的努力并无不妥。

      • > 用户空间的其他场景更适合采用编译型托管语言

        作为既用多种语言编写过用户空间应用,又开发过裸机运行的嵌入式固件的人,Rust实属难得一见的双料高手。

        • 除非那些用户空间应用是无头模式,否则说Rust擅长图形界面设计未免牵强。

          • 坦白说,https://github.com/timschmidt/egui-rad-builder 在上周的开发中进展相当顺利。若用 QT 构建类似应用,难度恐怕会大得多。

            尤其欣赏所有控件能在编辑器中实时呈现并即时编辑的流畅体验。imgui或许能提供类似效果,但相较于Rust,我认为C++的开发体验要痛苦得多。

            • 关于Rust GUI框架,还有Slint https://slint.dev

              (免责声明:我是Slint开发者之一。)

              • 几年前评估UI工具包时研究过Slint,界面很炫酷!唯一让我却步的是需要额外学习DSL来定义界面。如今我更倾向于少学语言、深学精通。能否不使用DSL直接使用Slint?

                • Slint确实需要使用其DSL定义界面,但我认为这并非学习全新语言。其难度不高于掌握任何其他GUI框架的API接口。

                  我曾专门为此写过博客文章,因为这是个常见问题:https://slint.dev/blog/domain-specific-language-vs-imperativ

                  • 我认为这种DSL并不理想。它看起来怪异(像是CSS与类混合的产物),且存在将用户锁定在特定产品的副作用。

                    你在文章中提到Rust的命令式代码显得更复杂,但这个问题可以通过为Rust添加“对象树”语法来解决,该语法允许创建对象树并像这样链接它们:

                        VBox margin=10:
                            Label text=label_text 
                            Button label="OK" onclick=on_ok_clicked
                    

                    这种语法不仅适用于UI,还能描述配置、数据库表等多种场景。我认为这比专有语言更优。

                    此外,若能在编辑器中直接绘制GUI,将使具备基础编程能力的开发者无需高昂的计算机科学教育背景也能参与界面设计。

                  • 感谢您的解答及详细阐述。关于GUI构建器与领域特定语言(DSL)协同工作的建议已记录。在egui快速应用开发构建器中,我通过操作描述UI的结构体(可导出/导入为JSON格式)来实现代码生成,本质上是类似的思路。我仍认为这种方式能减少上下文切换,相比学习新语言,频繁的来回切换更令我困扰。

                    话虽如此,这确实是项杰出工作!该语言领域完全容得下多种解决方案!

                  • 这篇帖子很有说服力。你将SQL称为DSL的观点让我眼前一亮。确实如此,但若让我列举常用DSL,此前从未想到过SQL。我的思维过于僵化,未曾意识到DSL的“领域”可以涵盖结构化数据查询这样宏大的范畴。这让我不禁思考:Rust是否可视为通用计算领域的DSL(当然不是通常意义上的DSL)?

                    和您回复的对象一样,我通常排斥领域限定于单个项目的DSL——过去的糟糕经历让我深陷其中:耗费时间学习后才发现完全偏离目标。还有个问题是当我作为软件唯一维护者却很少查看代码时,若Slint仅在此处使用,我就会担心三个月后调整界面时需要重新学习DSL。当然,任何非简单的框架API都可能存在类似问题,无论是否采用DSL。

                    话虽如此,读完你的文章后我对DSL的态度会更开放些。若真需要用Rust编写GUI,我会尝试Slint(不过这种可能性很低,毕竟我既不常写Rust也不常做GUI)。

            • 它如何处理本地化与辅助技术、设计师的UI/UX工具链、第三方组件生态系统?

              • egui通过AccessKit提供无障碍支持。我尚未为RAD构建器添加相关功能,这个建议很棒!

            • 但这已不是你需要做出的选择。Ubuntu官方数年前就宣布,所有为Linux原生开发的GUI应用都将采用Flutter框架,并为此项目投入大量工程师确保其成为核心组件。

              更重要的是,Dart/Flutter在这类场景中确实能带来极致愉悦的开发体验。

              • 确实,我对Dart/Flutter了解不多,但若非要选择,我会选它们、wxWidgets甚至Tcl/Tk,但绝不会选Rust。

              • 这对Dart团队是好消息。但这与纠正关于Rust的误解无关。我从不反对他人选择任何语言。我只是在用Rust开发,因为它契合我的特定需求。发现用它开发GUI和固件同样令人愉悦。

                • 我完全理解这些观点。但针对楼主评论的核心问题:我绝不会声称这两种语言的复杂度相当。如今选择已不再是Rust与C++的二元对立——它们都在本应收益甚微的场景中徒增复杂性。

                  • 多年经验告诉我,无论是否被运行时包装,复杂性始终存在。忘记它固然美好,直到它突然显现。某些应用可能永远不会触及抽象层设定的限制,但另一些则会迅速撞上壁垒。

                    正如俗语所言:“天下没有免费的午餐”。

                    • 对此反驳或许是:为极少数人可能遇到的抽象未来问题提前解决方案毫无意义——毕竟绝大多数人永远不会遇到这些问题。

                      这同样需要投入大量时间和资源。

                      更务实的说法是:通过观察前人使用相同工具在类似情境中的实践,你完全能判断自己是否会有特殊需求。

                    • > 试图为那些绝大多数人永远不会遇到的抽象未来问题预先解决毫无意义

                      > 这同样需要投入大量时间和资源。

                      讨论已变得相当抽象。回归具体案例:我开发的egui快速应用构建器在首日提交时就已正常运行。开发过程充满乐趣,其难度不亚于使用其他工具包构建GUI应用。这让我质疑你关于额外复杂性的论断。若想钻研深奥技术,Rust确实能施展黑魔法;但若像使用任何高级语言那样操作,同样能高效完成任务。这正是它在我眼中堪称稀世珍宝的原因之一。

                      有人不喜欢严格类型系统或借用检查器,但我发现它们揭示的错误在其他缺乏工具提示的语言中同样严重。这更让我珍视Rust。

            • 一周的开发让所有库都显得完美,但你是否已用Rust GUI交付过被广泛使用的实际产品?实际使用体验如何?更重要的是——存在哪些缺点?

              • 我使用egui已有两年,用它编写过十余个应用程序,涵盖图像处理到3D图形等多种功能。正如我在本讨论串其他地方提到的,尚未遇到它无法实现的需求。

                即时模式虽有批评者,但实践中我发现它极其灵活,生成的代码相对简洁,尤其当需要将图形快速复制到屏幕的内存映射区域时,egui能完美退居幕后。用户反馈普遍积极。

          • 说Rust在GUI领域表现出色有些牵强。

            顺便说一句,几乎所有语言都面临同样困境。究竟有哪些真正优秀的GUI工具包存在?又有哪门语言真正实现了与它们的良好集成?

            开发优秀的GUI工具包并实现集成,其难度甚至超过操作系统或关系型数据库管理系统(RDBMS)。(而能胜任RDBMS开发的优秀语言同样寥寥无几)

            • 这取决于你对“优秀”的定义。

              我认为Swift(甚至ObjC)与AppKit及UIKit堪称完美搭配。这些框架相当出色,我乐于使用它们。语言本身具备卓越的集成性——Swift本质上就是围绕这些框架设计的。这些工具包与macOS的融合度极高。

              我认为C#是相当不错的GUI语言,至少能与微软某款GUI工具包实现良好(或许不算完美)的集成。

              我认为Rust适合做GUI,但目前实现效果一般。封装式框架总是受限于Rust本身并非被封装的语言。相比封装框架,纯Rust框架缺少许多功能特性。

          • > 说Rust擅长GUI有点牵强。

            对此我深表赞同。尤其我接触过的Rust GUI库在文本布局/渲染方面,远未达到当今应有的水准(个人观点)。

            以egui为例:

            – 不支持双向文本重排
            – 缺乏字体回退机制,即使无需双向文本处理,也必须先获取合适字体才能渲染多数语言

            – 复杂字形处理同样缺失
            – egui的斜体效果极其糟糕,我无法理解为何连合成字体都呈现如此低劣的视觉效果

            CSS多年来始终出色地完成这些任务。因此我对Rust生态中缺乏同等强大的工具感到失望。即便仅考虑文本布局/渲染库,也只有`cosmic-text`的API方向稍显正确[1],但它同样存在缺陷——我找不到在文本间插入按钮(块级元素)的方法[2]。

            需说明的是,我并非针对egui本身,egui确实非常出色。据我所知,它已是Rust最完整的GUI库,能如此轻松构建图形界面实属难得。但必须指出它并非完美无缺,在GUI领域也未真正“出类拔萃”。

            此外我并不了解GTK、Qt等成熟GUI库的内联布局实现方式,或许我抱怨的功能在浏览器之外的场景本就难以实现。若有知情者,很想了解它们在这方面的对比表现。

            [1] CSS内联布局本就极其复杂,能实现这一点已属不易——毕竟新生的Rust库在时间成本上处于劣势,难以期待其具备多数CSS功能。

            [2] 这并非易事,因为双向文本重排应作用于整个段落而非仅按钮两侧,因此内联布局API必须处理文本中的非文本块。

            • 这份细节导向的真实问题清单非常棒!ChatGPT已在egui的Github上为每个问题找到了已知问题,说明这些问题已被发现并正在处理。

              不过,这里讨论的其他UI工具包(包括其他语言实现的)似乎都存在类似问题,这恰恰说明问题的复杂性。因此我完全赞同对egui和Rust的肯定——它们确实出色!而卓越的事物永远有进步空间!:)

          • 我也不认为egui在GUI方面表现糟糕。像Iced和Slint这类优质库值得关注,其中部分甚至具备egui般的出色无障碍支持。

            存在基于Rust开发的完整桌面环境,其核心即采用Iced:https://en.wikipedia.org/wiki/COSMIC_(desktop_environment)

          • 确实如此。据我所知,Rust的UI库目前都属于声明式和/或即时模式,这两种模式各有优势,但我并不认为它们适用于所有场景。有时你需要一个老派的命令式UI框架,配备功能强大的控件工具箱(比如AppKit、win32等),但这类框架在Rust生态中明显缺失。

            • https://www.gpui.rs/ 是相对较新的选择,被描述为“即时与保留模式的混合体”。或许值得根据你的用例尝试。

              即时模式确实需要重新适应,但目前我尚未遇到egui无法实现的任务,包括https://timschmidt.github.io/alumina-interface/这类相当复杂的应用。

            • Slint与“传统风格”的差异有多大?

              • 粗略看去,与其他Rust工具包的差异程度相当。它采用声明式设计,基础控件数量相对有限(需大量自定义控件代码),而像AppKit这类工具则采用命令式设计,自带大量开箱即用的丰富控件。

          • 确实如此,但这基本适用于所有语言——除了C++、C#、JavaScript/TypeScript和Dart。

            图形界面开发极其困难,绝大多数语言都难以获得高质量的GUI库。Rust尚属新生领域,众多开发者正致力于解决这个问题,未来必将有所突破。

          • GTK绑定完全够用。

        • > Rust是罕见的双优语言。

          此论成立,但特定领域中托管语言仍可能更胜一筹。

      • 并非所有人都持此观点。欲了解相反方向的研究实例,请参阅https://dioxus.notion.site/Dioxus-Labs-High-level-Rust-5fe1f…。

      • > 对于用户空间的其他场景,编译型托管语言是更优选择

        线程安全除外。

        • 无畏并发仅在极细粒度场景下才真正无畏。

          当线程共享内存资源时。

          但线程同样可能共享跨进程文件、内存映射区域、共享内存、数据库、分布式事务等资源——此时Send和Sync特性便无能为力。

          你似乎忽略了Haskell具备软件事务内存特性,Swift自6.0版本起也引入类似协议,OCaml、Scala、Koka等语言通过效果机制实现类似功能,而Dafny、Koka和Idris等语言则通过证明机制提供同等能力。

          • 正因如此,所有关注操作系统安全的人都应关注能力型操作系统。它们将发送与同步的概念提升至整个计算机的运行时层面进行实现。

      • > 对于用户空间的其他场景,编译型托管语言才是更优选择

        若开发纯应用程序[1],此言 或许 成立;但编写库时则不然。试想将C#库集成到Java程序、Java库集成到C#程序,或将二者集成到Python/Node/C/C++/Rust程序中的难度。无需运行时环境的优势不容忽视。

        [1] 除非你追求静态保证线程安全的便捷并行化。不过在多核CPU时代,谁还需要这种特性呢?

        • 操作系统进程间通信的存在自有其道理,如今微服务不是正风靡一时吗?Plan 9系统难道不优秀?

          • > 操作系统进程间通信存在自有其道理

            消息传递式IPC远比共享内存通信慢得多且效率更低,而进程间通信(无论消息传递还是共享内存)都远不如进程内多线程便捷。Rust是主流语言中唯一能实现安全 高效多线程的语言,无论是否采用托管机制。

            • 完全不是,正如我在另一个关联回答中解释的,只有当我们把用例限制在某个特定场景时,它才显得安全——而这种场景恰恰有助于推销这种叙事。

              • 嗯,现代操作系统确实存在诸多问题,其中文件系统非事务性访问位列我的三大痛点之一,但将这些问题归咎于Rust未免苛刻。这并非Rust的过错——毕竟没有内核开发者敢于最终推行那些在IT领域之外(如财务会计)经数十年验证的安全有效模式。

                Rust只是摘取它能够触及的果实,它基本坚守自身领域,同时尝试在某些方向拓展(比如Linux和嵌入式系统中的Rust应用)。我也渴望出现终极语言和终极内核,但恐怕你我此生都难得见。或许连我们的孙辈也无缘见证。

              • 这个?

                > 事实证明线程也可共享资源:跨进程文件、内存映射区域、共享内存、数据库、分布式事务

                对于多线程并行化,

                    - 跨进程文件
                    - 数据库
                    - 分布式事务
                

                这些场景并不适用,而Rust直接支持后两类场景:

                    - 内存映射区域
                    - 共享内存
                

                实际应用中,你提到的 “非常具体” 的场景才是最关键的,若没有Rust的Send和Sync特性及其自动强制机制,这类场景最难正确实现。

                • 判定哪些场景相关性强,取决于主导项目实现的架构师。

                  > 其实并不相关,而Rust直接支持后两者:

                  完全无关,因为Rust代码无法控制其他进程对资源的操作,你唯一能做的就是将访问操作包裹在unsafe代码块中,祈祷资源未被破坏。

                  • 你似乎混淆了 并发并行 的概念。自本讨论串首条评论起,我始终在谈论 并行化。两者确有重叠,但你列举的特性通常与并行化关联甚微,因此我称其“[不]真正相关”。而在多线程场景中(共享内存部分)确实需要这些特性时,Rust确实能提升正确性。

  7. Rust注定会变得和C++一样复杂。

  8. 这些特性听起来非常棒,对许多非内核场景(特别是泛化投影)也有助益。很高兴看到Linux推动语言发展。

  9. 是否有研究/项目/工具利用大型语言模型自动将部分/全部C代码转换为Rust?

    要求LLM根据聊天记录生成Rust代码,涵盖USB、I2C、GPU驱动程序——能否自动构建并测试?

    或者从其他“更小”的项目入手,如SQLite、Apache、Nginx等——可行吗?

    • 已有非LLM的研究尝试实现此目标并取得若干成功案例:https://github.com/immunant/c2rust

      LLM通常不适合此任务,因为它们本质上是近似器,无法始终确保正确性,从而引入隐蔽缺陷。或许结合某种形式化方法可行…但目前尚未见相关实践。现有测试套件无法捕获大量隐蔽缺陷,仅靠测试不足以解决问题。

      你提出的“小型”项目方案…仍不够精简。参考实际成功案例:https://github.com/immunant/c2rust?tab=readme-ov-file#uses-o

    • 国防高级研究计划局有意资助此类项目,但据我所知目前仅停留在意向阶段,尚未公布任何成果。

  10. 我感到恐惧。远胜于兴奋。

  11.     fn project_reference(r: &MyStruct) -> &Field {
            &r.field
        }
    
        unsafe fn project_pointer(r: *mut MyStruct) -> * mut Field {
            unsafe { &raw mut (*r).field }
        }
    
        // 对应的C语言代码如下:
        struct field *project(struct my * r) {
            return &(r->field);
        }
    

    我是Rust的重度使用者。我主要使用安全的Rust编程,偶尔也会涉及不安全的Rust。

    说实话,我认为Rust应该专注于它擅长的领域,而非勉强拓展到明显设计不佳的领域。比如,如果在Rust中实现链表的最佳方式是通过索引数组而非RefCell或其他机制呢?如果Rust永远无法以合理的方式实现链表呢?这有什么问题吗?我认为C和Rust之间应该有明确的界限。Rust留在它美好的Rust世界里,C留在它美好的C世界里。

    我不确定看到这样的代码会感到兴奋

        unsafe fn project_pointer(r: *mut MyStruct) -> * mut Field {
            unsafe { &raw mut (*r).field }
        }
    
    • 用Rust实现链表其实不难,完全可以像C/C++那样使用原始指针,再包裹一层安全API。

    • 实现链表的最佳方式是在集合类型中使用unsafe。你只需编写一次该类型,确保其绝对安全,然后继续处理下一项任务。我用双向链表实现的排序映射就从未出过问题。若用数组实现链表,编译器无法判断是否存在unsafe操作,所有问题依然存在。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注


京ICP备12002735号