Rust to C: New Compiler ‘Eurydice’ Bridges the Legacy Divide
A groundbreaking project aims to unlock the benefits of Rust’s memory safety and modern features for projects constrained by C compatibility, offering a path for gradual code migration and wider adoption.
The biggest surprise of the last two years, for many in the software development world, has been the growing demand not just for compiling C to Rust – driven by the well-known benefits of memory safety – but also for compiling Rust to C. This seemingly counterintuitive trend is fueled by the realities of maintaining software across a diverse landscape of legacy systems and specialized hardware.
Rust has rapidly gained traction as a compelling choice for new projects, lauded for its performance and security. Major companies, including both the author’s former and current employers, are increasingly adopting Rust for both entirely new applications and integrating it into existing codebases. Even the notoriously challenging world of Windows kernel driver development is now embracing Rust. However, a significant barrier to wider adoption remains: the sheer number of projects that rely on C and must remain compatible with a vast array of target architectures, operating systems, and toolchains.
“There will always be that one legacy use-case that prevents you from switching to Rust,” one developer explained, highlighting the challenges of updating long-lived projects. Consider a cryptographic library: developers often encounter requests to compile for obscure embedded targets with limited Rust support. Or perhaps a project needs to port a format library riddled with memory errors to the safer Rust ecosystem. These scenarios, coupled with the long-term support cycles of operating systems like Red Hat Enterprise Linux (RHEL), can indefinitely postpone a full migration to Rust.
That’s where a Rust-to-C compiler becomes invaluable. It offers a pathway for gradual transition, allowing developers to rewrite code in Rust while maintaining compatibility with existing C-based systems. This approach enables code refactoring and modernization, leveraging Rust’s advantages – data types, pattern matching, polymorphism, and, crucially, memory safety – without alienating existing users. Furthermore, compiling to C allows for a single authoritative codebase, with the C version automatically derived from the Rust source, ensuring consistency and simplifying maintenance.
“It only requires maintaining a single version,” a company release stated. “The Rust code is authoritative; the C code is derived from it automatically.” This also opens the door to identifying and addressing compatibility issues, allowing developers to pinpoint the specific scenarios where Rust support is lacking.
Enter Eurydice, a new compiler designed to translate Rust code into readable C. While the resulting C code is inevitably more verbose due to Rust’s whole-program monomorphization, the project prioritizes clarity and maintainability. Developers can already examine the output of compiling the libcrux library to C, and compare it to the original Rust code.
Eurydice operates by plugging directly into the MIR (Mid-level Intermediate Representation) level, leveraging the Charon compiler infrastructure to avoid redundant implementation efforts. This approach ensures a more faithful translation of Rust semantics and reduces the complexity of the compilation process. The translation process involves intricate tasks like whole-program monomorphization, converting pattern matches into tagged unions, and optimizing array repeat expressions.
The project’s developers have focused on generating readable C code, compiling Rust structs to C structs, and reconstructing control flow to avoid the use of goto statements. At a low level, the compiler addresses nuances like handling Rust arrays as C structs to preserve value semantics and managing the evaluation order in C, which is less strictly defined than in Rust.
However, the process isn’t without its challenges. The generated code can violate strict aliasing rules, requiring developers to compile with the -fno-strict-aliasing flag. Furthermore, the pervasive nature of monomorphization can lead to code bloat, necessitating a configuration system to manage the placement of specialized code instances.
Eurydice currently generates code compatible with C11, C++20, and C++17 standards, navigating the complexities of Rust’s enum values and designated initializers. The project is actively integrating with cryptographic libraries from Microsoft and Google, and benefiting from contributions from the open-source community, including GitHub users @ssyram and @lin23299.
The development team has set an ambitious goal: to enable the extraction of the entire Rust standard library via Eurydice by 2026. “This is non-trivial, but I believe this achievement is within reach,” one of the project’s leads stated. Future work includes improved support for dynamic traits via vtables and leveraging Charon’s monomorphization capabilities.
The project’s name, drawing from Greek mythology alongside Aeneas and Charon, reflects the arduous journey of generating C code. As the developer behind Eurydice explained, the myth of Eurydice resonated with the challenges faced: “I thought I was saved from the hell of generating C code, and was going to go back to the world of the living, but alas, no.”
