首页 时政热点 科技头条 智能AI 安全攻防 数码硬件 开发者生态 汽车 游戏 社会热点 开源推荐 医疗健康 归档 标签 关于

使用 Eurydice 将 Rust 编译为可读的 C

摘要

For humans, by humans Every article on LWN.net is written for humans, by humans. If you've enjoyed this article and want to see more like it, your subscription goes a long way to keeping the slop at b...

the Rust and code Eurydice humans for compiler tools with
2026-10-10 1 阅读 约7分钟阅读 peter_d_sherman
分享:
字号:
为人类,由人类撰写 LWN.net 上的每一篇文章都是为人类、由人类撰写。如果您喜欢这篇文章并且希望看到更多类似的文章,那么您的订阅将有助于避免这种情况的发生。我们提供为期一个月的免费试用订阅(无需信用卡)以帮助您入门。作者: Daroc Alden,2026 年 1 月 30 日 几年前,编译 Rust 代码的唯一方法是使用以 LLVM 作为后端的 rustc 编译器。从那时起,包括 Mutabah 的 Rust 编译器 (mrustc)、GCC 的 Rust 支持 (gccrs)、rust_codegen_gcc 和 Cranelift 在内的多个项目在 Rust 编译器实现多样化方面取得了巨大进展。最近的此类项目,Eurydice,有一个更雄心勃勃的目标:将 Rust 代码转换为干净的 C 代码。这在高可靠性软件中特别有用,因为现有的验证和合规性工具需要 C。在这些工具更新为与 Rust 一起使用之前,Eurydice 可以为这些项目提供更平滑的过渡,并为具有 C 编译器但没有可用 Rust 编译器的环境提供垫脚石。例如,Eurydice 已被用来编译一些从 Rust 到 C 的后量子密码例程。 Eurydice 于 2023 年启动,包括 MIT 许可证下的一些代码和 Apache-2.0 许可证下的一些代码。它是 Aeneas 项目的一部分,该项目致力于开发几种与将形式验证工具应用于 Rust 代码相关的不同工具。各种 Aeneas 项目由 Inria(法国国家计算机科学研究机构)和 Microsoft 雇用的一群人维护,但他们也接受外部贡献。 Eurydice 遵循与许多编译器相同的通用结构:获取 Rust 程序,将其转换为中间表示 (IR),通过一系列传递修改 IR,然后将其输出为较低级语言(在本例中为 C)的代码。 Jonathan Protzenko 是 Eurydice 最多产的贡献者,他在一篇博客文章中解释了该项目的方法。然而,与其他编译器不同的是,Eurydice 关注的是保留代码的整体结构,同时删除 Rust 中存在但 C 中不存在的结构。例如,考虑一下这个 Rust 函数,它使用最大公分母计算两个数字的最小公倍数: fn gcd(a: u64, b: u64) -> u64 { if b == 0 { a } else { gcd(b, a%b) } } fn lcm(a: u64, b: u64) -> u64 { (a * b) / gcd(a, b) } 以下是 Eurydice 如何将这些函数编译为 C 语言: uint64_t example_gcd(uint64_t a, uint64_t b) { uint64_t uu____0; if (b == 0ULL) { uu____0 = a; } else { uu____0 = example_gcd(b, a % b);返回uu____0; } uint64_t example_lcm(uint64_t a, uint64_t b) { uint64_t uu____0 = a * b;返回 uu____0 / example_gcd(a, b);这段 C 代码是否算作“可读”可能取决于个人喜好。然而,它确实保留了代码的结构。即使是原始的评估顺序也可以通过在需要定义顺序时添加额外的临时变量( example_lcm() 中的 uu____0 )来保留。 (Rust 保证,如果乘法溢出并导致恐慌,这种情况会在调用 example_gcd() 引起任何副作用之前发生,但 C 只保证乘法在单独的语句中执行。)使用 rustc 编译相同的函数会产生一对充满位旋转操作的纠缠循环,这适合机器代码输出,但可读性差得多。当然,并非所有 Rust 程序都能如实地用 C 语言表示。例如,使用迭代器而不是范围的 for 循环需要编译为 while 循环,调用 Eurydice 的一些支持代码来管理迭代器的状态。更重要的是,C 没有泛型的概念,因此 Rust 代码在转换过程中需要单态化。这可能会导致一个函数有几种不同的实现,这些实现仅在类型上有所不同——通常,更惯用的 C 方法是使用宏或 void * 参数。动态大小类型的实现也带来了一定的挑战。在 Rust 中,可以定义一个结构,其中一个字段没有固定大小 - 就像 C 中的灵活数组成员一样: struct DynamicallySized { header: usize, my_data: U, // 编译器不知道 U 的大小,这里 } 但是,如果该结构是通用的,并且该类型的通用用户之一为灵活大小的字段提供了一个具有已知大小的类型,则编译器可以利用该知识在适当的情况下省略边界检查。让 foo: DynamicallySized<[u8; 4]> = ...; // 没有发出边界检查,因为已知数组大小为 4:let bar = foo.my_data[2];这种分离(代码的某些部分可能知道类型的大小,而另一些则可能不知道)是 C 中需要保留的重要语义细节,因为它与形式验证的可能性相互作用。如果 Eurydice 编译 DynamicallySized 以在各处使用灵活的数组成员,则分析
这篇文章对您有帮助吗?

订阅66必读

每日精选科技资讯,直达你的邮箱