Eurydice compiles Rust to readable C, but only for small programs

LWN looks at Eurydice, a tool in the Aeneas project whose goal is to convert Rust code into clean C code. That is a more ambitious aim than other recent projects that diversify Rust compiler implementations, such as Mutabah's Rust Compiler (mrustc), GCC's Rust support (gccrs), rust_codegen_gcc and Cranelift. The article says the idea is especially useful in high-assurance software, where existing verification and compliance tools expect C. Until those tools can work with Rust, Eurydice could offer a smoother transition, and a stepping-stone for environments that have a C compiler but no working Rust compiler. It has been used to compile some post-quantum-cryptography routines from Rust to C.

Eurydice was started in 2023, with some code under the MIT license and some under Apache-2.0. It is part of the Aeneas project, which develops tools for applying formal verification to Rust code. The Aeneas projects are maintained by people employed by Inria (France's national computer-science-research institution) and Microsoft, and they accept outside contributions. Jonathan Protzenko, the most prolific contributor to Eurydice, has a blog post explaining the approach.

The design follows the usual compiler shape: take a Rust program, convert it to an intermediate representation, run a series of passes over it, and emit a lower-level language, here C. The difference is that Eurydice tries to preserve the overall structure of the code while removing constructs that exist in Rust but not in C. The article's example is a recursive gcd function and an lcm function built on it. Eurydice emits C with the same if/else structure and the same recursion, adding a temporary variable (uu____0) in lcm to keep Rust's evaluation order: Rust guarantees that an overflow panic in the multiplication happens before any side effects of calling gcd, and C only guarantees that if the multiplication is a separate statement. Compiling the same functions with rustc produces a pair of entangled loops full of bit-twiddling, which suits machine code but is much harder to read. Whether the generated C counts as readable is, the author says, probably a matter of individual taste; it does keep the structure.

Not every Rust program maps cleanly to C. For loops over an iterator, rather than a range, become while loops that call Eurydice support code to manage iterator state. C has no generics, so Rust code must be monomorphized, which can yield several implementations of a function that differ only by type, where idiomatic C would more often use macros or void * arguments.

Dynamically sized types are another hard case. In Rust a struct can have a field of unknown size, like a flexible array member in C, and a generic user can give that field a known size such as [u8; 4], letting the compiler skip bounds checks. To keep that distinction visible to formal verification, Eurydice emits two different types: one with a flexible array member and one with a known-length array member. Converting between them is a no-op at run time but technically violates C's strict-aliasing rule, so Protzenko recommends compiling Eurydice-generated code with -fno-strict-aliasing. Using a flexible array member everywhere would make analysis flag bounds checks that Rust never required, and adding checks would create error paths that do not exist in the Rust source.

The approach is not new. KaRaMeL, on which Eurydice is based, does the same structure-preserving compilation for F*, a dependently typed functional language used to develop cryptographic libraries. Eurydice does not parse or typecheck Rust itself; it uses another Aeneas tool, Charon, to extract the parsed and preprocessed program from rustc. Charon dumps rustc's MIR as JSON along with the compiler flags needed to understand the build. Eurydice reads that JSON, converts it to KaRaMeL's intermediate representation, runs small passes to remove Rust-specific details, and hands over to the same code generation KaRaMeL uses for F*.

The verdict is mixed. Eurydice does not currently scale much beyond small examples, and when the author tested Charon on a variety of Rust packages, it was routinely foiled by more recent Rust features such as const generics. In its current form Eurydice works best for small, self-contained programs that avoid complex Rust features, and within that niche it works well. But small self-contained code is also the easiest to rewrite by hand, so adopting Eurydice is probably only worthwhile if the Rust original will keep changing and you want an automatic way to keep the C in sync.

Key facts

  • Eurydice, part of the Aeneas project and started in 2023, converts Rust to structure-preserving C, aimed at high-assurance software where verification and compliance tools expect C.
  • It builds on KaRaMeL (which does the same for F*) and uses Charon to pull rustc's MIR out as JSON; it has been used to compile some post-quantum-cryptography routines from Rust to C.
  • Rust features without a C equivalent are handled by monomorphizing generics, adding support code for iterator loops, and emitting two types for dynamically sized types; Protzenko recommends compiling the output with -fno-strict-aliasing.
  • The author says Eurydice does not currently scale much beyond small examples, and Charon was routinely foiled by features such as const generics in testing.
  • Adopting it is probably only worthwhile if the Rust code will keep being updated and you want an automatic way to keep the C in sync.

Why it matters

Several projects now offer alternatives to rustc: mrustc, gccrs, rust_codegen_gcc and Cranelift. Eurydice has a different goal, producing clean C from Rust rather than machine code. The article argues this is especially useful in high-assurance software, where existing verification and compliance tools expect C. Until those tools learn to handle Rust, converted C could ease the transition, and it could also serve as a stepping-stone for environments that have a C compiler but no working Rust compiler. A concrete example is the use of Eurydice to compile some post-quantum-cryptography routines from Rust to C.

Who it affects

Mainly teams building high-assurance software whose verification and compliance toolchains still expect C, and projects targeting environments with a C compiler but no working Rust compiler. It also touches people in the Aeneas ecosystem: the projects are maintained by a group employed by Inria and Microsoft, who accept outside contributions.

How to use it

The pipeline is Rust source, then Charon (which extracts rustc's parsed and preprocessed program as MIR in JSON, with compiler flags), then Eurydice (which converts to KaRaMeL's intermediate representation, runs passes, and generates C). Compile the generated C with -fno-strict-aliasing, as Protzenko recommends. Stick to small, self-contained Rust programs that avoid complex features. The article suggests it pays off mainly when the Rust original will keep being updated and you want the C kept in sync automatically. The code is under the MIT license and Apache-2.0, depending on the part.

How solid is it

The write-up is a hands-on review, and the author's own testing of Charon on a variety of Rust packages is the main evidence on limits. Eurydice has existed since 2023 and builds on KaRaMeL, which already does structure-preserving compilation for F*. Within its niche of small, self-contained programs the article says it works well, and the generated code keeps the original structure except where extra intermediate variables or glue code are needed. No benchmarks, performance numbers or code-size comparisons between Eurydice output and rustc output are given.

Risks and caveats

The article states plainly that Eurydice does not currently scale much beyond small examples, and that Charon was routinely foiled by more recent Rust features such as const generics. Not all Rust programs can be faithfully represented in C: monomorphization can produce several near-duplicate functions, and iterator loops need Eurydice support code. Converting between the two dynamically-sized-type representations technically violates C's strict-aliasing rule, which is why -fno-strict-aliasing is recommended. Whether the output is readable is described as a matter of taste. Small self-contained code is also easy to rewrite by hand, which weakens the case for the tool in many situations.

“In its current form, Eurydice works best for small, self-contained programs that avoid complex Rust features.”

— LWN article on Eurydice