LWN.net Logo LWN.net News from the source LWN

User: Password: | | Subscribe / Log in / New account

Oxidizing Ubuntu: adopting Rust utilities by default

[LWN subscriber-only content]

By Joe Brockmeier March 18, 2025

如果一切按计划进行,Ubuntu 项目很快将用 Rust 编写的实现来替换许多传统的 GNU 工具,例如 uutils 项目创建的那些,我们在二月份报道过。在一个 Linux 发行版的核心中全面替换核心实用程序并非小事,这就是为什么 Canonical 的工程副总裁 Jon Seager 发布了 oxidizr。它是一个命令行实用程序,可帮助用户轻松启用或禁用基于 Rust 的实用程序,以测试它们的适用性。Seager 呼吁大家协助测试,并希望用户在 Ubuntu 25.10(计划于 2025 年 10 月发布的临时版本)进行可能的切换之前,提供他们的使用体验反馈。到目前为止,Ubuntu 社区的反应似乎是积极的,但对如此重大的变化略有怀疑。

Ubuntu 的未来 20 年

Ubuntu 在去年庆祝了自 2004 年首次发布以来的 20 周年。Seager 回顾了这一里程碑,并在 2 月份发布了他对 Ubuntu 未来 20 年的愿景。他为未来设定的主题之一是现代化,呼吁项目不断根据用户的需求评估发行版的基础:

我们应该深入研究 Ubuntu 默认附带的工具 - 选择那些以弹性、性能和可维护性为核心的工具。在开源社区中,有无数的工具被重新设计和重新构想的例子,它们使用了相对较新的工具和实践。我个人最喜欢的一些包括命令行实用程序,如 ezabathelix,新的 ghostty 终端模拟器,以及更基础的项目,如 uutils 用 Rust 重写的 coreutils。这些项目都处于不同的成熟度水平,但都展示了对更现代化的类 Unix 体验的愿景,该体验强调弹性、性能和可用性。

3 月 12 日,Seager 发布了后续文章,介绍了他的计划,即开始默认采用一些工具——着眼于在下一个 Ubuntu 长期支持 (LTS) 版本 26.04 中使用它们。切换的基本原理主要是 “Rust 端口更容易实现的增强的弹性和安全性”。他引用了 Rust 核心开发者 Niko Matsakis 的一篇博客文章。简而言之,这篇文章是关于 Matsakis 使用 Rust 编写(或重写)基础软件的愿景;也就是说,“构成一切基础的软件”。

喜欢你读到的内容吗? 免费试用 LWN 1 个月,无需信用卡。

那些一直在关注关于使用 Rust 的持续辩论和讨论的人们,会在 Matsakis 支持它的论点中找到熟悉的主题:Rust 提供了 C/C++ 的性能,而无需开发者达到完美,它提供了可靠性,并且无论经验水平如何,都能提高开发者的生产力。它的可靠性使其特别适合基础软件,因为“当基础崩溃时,顶层的一切也会崩溃”。考虑到 Ubuntu 的广泛采用,Seager 写道,“我们有义务确保我们提供的软件是最具弹性和最值得信赖的”。

Seager 还认为,拥抱 Rust 将有助于实现他为 Ubuntu 制定的另一个目标,即增加贡献者的数量。这并不是因为 Rust 一定比 C 更容易使用,而是因为它提供了一个框架,使得贡献者更难提交潜在的不安全代码。据推测,虽然没有明说,但这将使 Rust 成为对那些有兴趣贡献但出于某种原因对用 C 编程不感兴趣的人来说,更具吸引力的语言。

oxidizr

仅仅抽象地认为 Rust 实用程序会更好,甚至是可行的,对于 Ubuntu 来说,是无法取代实践经验的。为此,Seager 创建了 oxidizr,作为一种以相对较低的风险快速换入(和换出)Rust 实用程序来代替传统对应物的方法。他在 3 月 7 日发布了第一个版本 1.0.0。它以 Apache 2.0 许可提供,并且正如人们可能期望的那样,是用 Rust 编写的。

该项目尚未针对 Ubuntu 进行打包,Seager 也没有设置个人软件包存档 (PPA),供用户使用 APT 安装 oxidizr。GitHub 上有二进制版本,或者用户可以使用 cargo 安装该工具:

  $ cargo install --git https://github.com/jnsgruk/oxidizr

二进制版本可能是最容易上手的方式,因为 oxidizr 需要 exists 函数,该函数位于 fs 模块中,但 exists 是在 Rust 1.81.0(于 2024 年 9 月发布)中添加的,而 Ubuntu 24.10 中的 Rust 版本仍然是 1.80.1。我使用 rustup 安装了最新的稳定版 Rust,然后使用 cargo 安装了 oxidizr

oxidizr 实用程序将一组可以独立替换的实用程序称为 “实验”。实验是 Rust 模块,用于定义要安装(或删除)的软件包,并处理实用程序的重命名以启用或禁用 Rust 版本的使用。当前的一组实验 包括用 uutils coreutilsfindutilsdiffutils 替换 GNU coreutilsfindutilsdiffutils,以及用基于 Rust 的 sudo-rs 替换传统的 sudo

例如,要试用 sudo-rs,用户可以运行以下命令:

  # oxidizr enable --experiments sudo-rs

这将从 Ubuntu 软件包存储库安装 sudo-rs 软件包,备份 sudo 二进制文件,并创建一个指向 Rust 二进制文件 (/usr/lib/cargo/bin/sudo) 的 /usr/bin/sudo 符号链接。要启用所有实验,用户可以使用 all 目标代替:

  # oxidizr enable --experiments --all

最后,要将系统恢复到传统实用程序并从系统中删除替换软件包:

  # oxidizr disable --all

根据 Seager 的说法,oxidizr 适用于 24.04 LTS 之后的 Ubuntu 的所有版本,但 uutils diffutils 实验仅在 Ubuntu 24.10 或更高版本上受支持。他确实敦促用户为了安全起见,开始在虚拟机或其他非生产工作站或服务器上进行测试。Seager 报告说他没有遇到很多问题,但他遇到了一种不兼容性:uutils cpmvls 替换尚不支持 -Z 标志,该标志用于设置文件或(在 ls 的情况下)打印文件的 SELinux 上下文。

在我简短的测试中,我没有遇到任何关于 uutils 版本的实用程序或 oxidizr 为了换入它们而对系统所做的更改的问题。但是,我确实注意到 oxidizr 不会对系统的 man 页面进行任何更改。即使 GNU 实用程序已被 uutils 版本替换,GNU man 页面仍然保留,因此 "man cp" 仍然显示 GNU 版本。最好也切换 man 页面,以便向用户公开 uutils 文档以及实用程序本身的任何差距。

反响

Fern Dziadulewicz 询问 转向 uutils 是否意味着 “Ubuntu 实际上有点走向 GNUlessness”,就像一些避开 GNU 组件的其他 Linux 发行版一样。Seager 回应 说,人们不应过度解读这种变化:

这不是任何明确偏离 GNU 组件的象征 - 它实际上只是用更现代的等效物替换 coreutils。 当然,许可证是不同的,这是一个考虑因素,但绝不是决策的驱动因素。

Joseph Erdosy 对此回应并不满意,他 写道,如果 Ubuntu 进行了此项更改,他将迁移到 Fedora 或 Rocky Linux。他说他喜欢 Rust 以及更好、内存安全的替代方案的想法,但他不高兴的是,最大的 “氧化” 项目是 GPL 许可的代码的 MIT 许可的重写。

这项决定似乎与公司弃用 GPL 软件而支持更宽松许可的替代方案的更广泛趋势相一致,通常以 “现代化” 为幌子。然而,实际影响是显而易见的:自由软件越来越被共同选择进入专有生态系统,削弱了使 Linux 成功的原则。

其他一些用户很快同意了 Erdosy 的观点,然后 Ian Weisser 宣布,他正在将该主题置于 “慢速模式”,以 “防止堆积,直到开发人员有机会做出回应并保持此主题具有建设性”。此后不久,Seager 回应 说,他不同意这一潜在举动对 Ubuntu 或其社区构成威胁。他重申,这并不表示政治议程或更广泛地偏离 GPL 许可的软件,并表示 Canonical 自己的大多数软件都是并且将继续是 GPL 许可的。

Ubuntu 是我们策划以构建发行版的软件集合。这是一个致力于发布我们能找到的最新、最好的开源软件的项目。没有证据表明 uutils 维护人员存在不正当行为、不良行为或不良意图 - 他们是一个深思熟虑、专注的社区,他们正在构建自己的软件,甚至在某些情况下还会回馈 GNU coreutils。他们正在取得我认为我们应该在未来几年内渴望用 Ubuntu 实现的成就,我仍然致力于让他们有机会成功 - 注意到我们和其他人需要与他们密切合作,以解决与语言环境、selinux 支持和其他问题。 如果当前情况发生变化,并且我们认为 uutils 项目的利益不再与 Ubuntu 的利益一致,我们可以更改我们选择与 Ubuntu 一起发布的 coreutils 软件包。

Sergey Davidoff 想知道,当安装了具有相同功能的多个程序时,用于指定默认应用程序的 Debian alternatives system 是否不足以用于试验 Rust 实用程序。Julian Andres Klode 回复 说,alternatives system 不适用,因为现有软件包需要合作。他还回应了另一位用户 "rain",他 提出了允许用户切换单个命令的想法。Klode 说,允许用户在每个命令级别选择 Rust 和非 Rust 实现是一个坏主意,因为它会使生成的系统难以支持。

Liam Proven 询问 Ubuntu 的 x86_64 和 Arm 以外的架构(如 s390 和 ppc64le)上的支持情况,因为 “LLVM Rust 工具链仍然有点不成熟,并且其他架构的代码生成不足”。Uutils 项目创始人兼 Ubuntu 开发人员 Sylvestre Ledru 询问 Proven 是否有任何错误报告要分享,因为 Firefox 多年来一直在使用 LLVM Rust 工具链在这些架构上发布 Firefox。他指出,uutils 已经成功地在 Debian 和 Ubuntu 上构建,并将这些架构作为目标已有几年了。

后续步骤

Seager 说,他已经与 Ledru 会面,讨论了使 uutils coreutils 成为 Ubuntu 25.10 中默认设置的想法,Ledru 认为该项目已准备好接受该级别的公开。他说,现在只是具体问题,Ubuntu Foundations 团队已经在制定计划,以在下一个发布周期中实现这一点。他确实承认需要谨慎,并且对如果进行切换意味着损害 Ubuntu LTS 版本的稳定性或可靠性,他愿意 “缩小雄心”。如果切换不起作用,则应该很容易在明年 LTS 发布之前恢复原状。

迄今为止,Ubuntu 似乎是第一个认真考虑切换到 uutils 的主要 Linux 发行版。如果 Ubuntu 25.10 附带 uutils coreutils,对于 uutils 项目来说将是一次重大胜利,因为它将比迄今为止享受的用户群更大。 “氧化 Ubuntu” 实验有可能加速 Rust 的采用并激发更多尝试用 Rust 替换基于 C 的实用程序,或者如果 Ubuntu 遇到严重问题,可能会产生令人不寒而栗的效果。无论哪种方式,该项目都应该对更大的社区具有启发意义。

to post comments

resource usage concerns

Posted Mar 18, 2025 17:00 UTC (Tue) by arachnist (subscriber, #94626) [Link] (29 responses) a friend of mine has stumbled upon excessive memory usage in more[0], and i wonder how many more issues like that will start popping up when more people start using these. worst case scenario, we're going to see one of the funnier ubuntu releases in recent memory. ;) [0]: https://github.com/uutils/coreutils/issues/6397

resource usage concerns

Posted Mar 18, 2025 17:17 UTC (Tue) by jzb (editor, #7867) [Link] I wonder how many more issues like that will start popping up when more people start using these. It will be interesting to see, won't it? Definitely the kind of testing uutils needs to be a legit replacement. Whatever warts the GNU utilities may have, they've gotten a lot of use over the years and have been road-tested quite well. I am eager to see the results.

resource usage concerns

Posted Mar 18, 2025 18:11 UTC (Tue) by LtWorf (subscriber, #124958) [Link] (24 responses) Memory leaks are not considered problematic in Rust, they do not cause compilation errors like other memory bugs.

resource usage concerns

Posted Mar 18, 2025 19:01 UTC (Tue) by khim (subscriber, #9252) [Link] (2 responses) I think one of the insights that actually made Rust possible is an observation about complete infeasibility of declaring memory leaks as errors. Tracing GC “doesn't have memory leaks”, but only if you define “memory leaks” in an extremely perverse fashion: why would I care that my program “doesn't have memory leaks” but instead wastes gigabytes of memory in the “inactive caches”? If it looks like a ~~duck~~ memory leak, ~~swims~~ acts like like a ~~duck~~ memory leak, and ~~quacks~~ makes life painful like a ~~duck~~ memory leak, then it probably is a ~~duck~~ memory leak – and proponents of tracing GC wouldn't convince me otherwise. And once you realize that elimination of memory leaks in layman sense is impossible you immediately realize that tracing GC is not needed and can think about how to live in a world without one. And then you end up with Rust – not by design but by accident and/or observation.

resource usage concerns

Posted Mar 18, 2025 23:49 UTC (Tue) by shahms (subscriber, #8877) [Link] (1 responses) The Java documentation notoriously had to come up with another term after Sun marketed so heavily on GC eliminating memory leaks, so instead of "memory leaks" Java has lots and lots of "unintentional object retention".

resource usage concerns

Posted Mar 19, 2025 8:57 UTC (Wed) by taladar (subscriber, #68407) [Link] A slightly different type is the space leak due to lazy evaluation in Haskell (while we are on the topic of alternate terminology for similar problems).

resource usage concerns

Posted Mar 18, 2025 19:33 UTC (Tue) by mb (subscriber, #50428) [Link] (10 responses)

Memory leaks are not considered problematic in Rust Of course they are considered problematic in Rust. And leaks are hard to code by accident in Rust. One basically can't simply forget to destroy an object due to automatic drops. (and "forget" is an explicit operation). Of course it's possible to program memory leaks by creating cyclic references (although cyclic references are harder in Rust than in most other common languages) or lists that are never freed. But Rust makes it much harder than say C to just accidentally forget to free something in an obscure error path.

resource usage concerns

Posted Mar 19, 2025 14:08 UTC (Wed) by cultpony (subscriber, #167240) [Link] (9 responses) Usually the more common case of "memory leak" in Rust is that the compiler has pushed the deallocation so far back that it's effectively happening on program termination. Sometimes you have to be explicit about when you'd like memory to be freed rather than letting the compiler figure out that it is in fact valid to drop the data by waiting for program termination (which Rust considers to be fine too).

resource usage concerns

Posted Mar 19, 2025 23:05 UTC (Wed) by NYKevin (subscriber, #129325) [Link] (8 responses) Just to clarify for those less familiar with Rust: Rust is a systems language with manual memory management, just like C, but drenched in a thick layer of syntactic sugar (to automatically free things when you're done using them) and static analysis (to detect when you free something before you're done using it). It does not make arbitrary decisions about when to deallocate things (contrast with a GC'd language, which does make such decisions). If the compiler did not deallocate something for you, it means that you have (knowingly or not) asked the compiler to keep that thing alive. In most cases, if something no longer needs to exist, you can std::mem::drop() it, or just return from whichever scope owns the allocation. drop() is a safe function, meaning the compiler will not let you use anything that has potentially been dropped (by either means), and in fact drop() is really just a convenience function that takes ownership, does nothing, and immediately returns. You can't drop static variables or anything that you don't own. There are objects with "more complicated" ownership models than that (a simple example being Rc/Arc), but those objects still have some notion of dropping (you can std::mem::drop() any variable, but if that variable is participating in some shared ownership chicanery, the shared allocation might outlive it). There is also one other catch: Stack allocations always last until the function returns. That's not a Rust limitation, it's just how the stack works (at least, in any language that has a call stack). If a stack variable is moved from (or dropped early, but that's equivalent to moving it), what actually happens is that the variable's contents are memcpy'd into the new location, the drop flags are updated to indicate that the variable is now uninitialized garbage (and must not be dropped or otherwise used again), and the variable binding is deleted from the current namespace (so you can't use it again). But the stack allocation is still physically occupied until the function returns. This is rarely a problem because we usually allocate large objects on the heap (plus, the optimizer can do all sorts of things with the physical stack layout anyway).

resource usage concerns

Posted Mar 20, 2025 1:06 UTC (Thu) by wahern (subscriber, #37304) [Link] (6 responses)

Stack allocations always last until the function returns. That's not a Rust limitation, it's just how the stack works (at least, in any language that has a call stack) That's not how C works. Automatic variables, including VLAs, are scoped to blocks. If they weren't, than you'd have problems with loops and stack overflow. Allocations using the common "alloca" builtin do last for the entire function, but VLAs were deliberately given different semantics.

resource usage concerns

Posted Mar 20, 2025 4:13 UTC (Thu) by NYKevin (subscriber, #129325) [Link] Yes, sure, you can allocate and deallocate multiple separate blocks per function, but the point is that you cannot point to an arbitrary stack allocation and say "just deallocate that right now, without touching anything else." The physical structure of the stack is incapable of representing such an operation.

resource usage concerns

Posted Mar 20, 2025 13:40 UTC (Thu) by tialaramex (subscriber, #21167) [Link] (4 responses) IIUC Although the scope ends, the allocation does not. The loop re-uses the allocation, and that's how some of the GC'd languages get that design mistake where they re-assign a single variable for each iteration rather than destroying that variable and conjuring a new one into existence with the same name. In C because it doesn't have RAII or GC the behaviour looks like it could be either and so it's harder to realise that one of these approaches is wrong. [If only one language had that mistake, or, even if several did this but they don't regard it as a mistake and fix it, that would be a different matter but in fact this mistake has happened several times and been fixed in IIRC at least C# and Go]

resource usage concerns

Posted Mar 20, 2025 22:39 UTC (Thu) by wahern (subscriber, #37304) [Link] (3 responses) The stack does shrink. Example program:

#include <stdio.h> #include <stdint.h> #include <string.h> attribute((noinline)) static void showfp(unsigned n, intptr_t otop) { intptr_t top = (intptr_t)__builtin_stack_address(); printf("n:%u off:%td\n", n, (otop > top)? otop - top : top - otop); } int main(void) { unsigned n = 0; intptr_t top = (intptr_t)__builtin_stack_address(); while (1 == scanf("%u", &n)) { char buf[n]; memset_s(buf, n, 0, n); showfp(n, top); } return 0; }


For `echo 200 100 5 | ./a.out` I get:
> ```
n:200 off:272
n:100 off:176
n:5 off:80

As the size of successive stack allocations decrease, so does the frame size.

resource usage concerns

Posted Mar 21, 2025 13:39 UTC (Fri) by tialaramex (subscriber, #21167) [[Li