We’ve recently been able to invest in some much-needed maintenance on and focused improvements to the C2Rust transpiler, which translates C code into unsafe Rust. Below is a highlights reel of some of the more substantial changes in version 0.21, underlining improvements toward generating more idiomatic Rust and a couple places where we’d most appreciate feedback. For a more comprehensive list of all changes, including many correctness improvements and bugfixes in this release, see the release notes!

Highlights

C2Rust is on the Compiler Explorer!

C2Rust has been added to Matt Godbolt’s Compiler Explorer! You can now try the latest development version of C2Rust there at any time.

Portable types

C2Rust now preserves the portability of C code using types such as size_t and int32_t.

Previously, these types were translated into definitions ultimately backed by Rust’s C compatibility types, e.g. c_ulong or c_int. This is because Clang ultimately defines fixed-size types like int32_t in terms of C’s built-in types such as int and long. C2Rust follows typedefs and translates them as Rust type aliases, but did not treat the definition of size_t (for example) differently from any other typedef that the program might contain. C2Rust now recognizes the definitions of these types that have a more faithful translation into Rust and defines the C type in terms of that portable Rust type (e.g. usize or i32). Type aliases are still emitted, so programmers can keep the C names for comparison with the original C code or refactor out the aliases for more idiomatic Rust code.

Preprocessor macro conversion

The C preprocessor plays an important role in the way idiomatic C is written, but the freeform token-substitution nature of the preprocessor means it is difficult to provide equivalent Rust translations of many of the ways it can be used. However, many of the most common applications of the preprocessor are easy to express in Rust, such as simple #defines to give names to constants. C2Rust previously had some experimental support for translating these (enabled with the --translate-const-macros flag), but it was unreliable and broke translation for many programs.

We’ve revamped our handling of const macros, and enabled translation of a conservative subset of simple #define macros by default. This preserves more information about programmer intent from the C sources, providing substantially more idiomatic translation of simple programs using #define in this way:

#define CIRCLE_DEGREES 360
int degrees = CIRCLE_DEGREES;

is now translated to:

pub const CIRCLE_DEGREES: core::ffi::c_int = 360 as core::ffi::c_int;
let degrees: core::ffi::c_int = CIRCLE_DEGREES;

Hopefully, this can improve a common pain point in human cleanup and maintenance of C2Rust-translated codebases, as pointed out in Collin Richards’ recent article on porting tmux to Rust (in particular, the new behavior handles almost all of the uses of preprocessor macros in tmux). If the new default does cause any regressions, it can be overridden by passing --translate-const-macros=none when invoking c2rust transpile. And please report any such regressions please on our issue tracker so we can fix them!

And for codebases with particularly involved uses of the preprocessor, check out the Hayroll project, which builds atop C2Rust to provide more advanced coverage of preprocessor macros, including function-like macros and usage of macros for platform detection.

Repeat expressions

C2Rust previously expanded zero values for initializers, which are often implicit in C:

int x[50] = {};

Now, this will be translated to a repeat expression, instead of writing out a bunch of zeroes in the translated program:

let mut x: [std::ffi::c_int; 50] = [0; 50];

This avoids bloating transpiled sources and preserves readability.

Dependency updates

C2Rust is now additionally compatible with CMake 4 and LLVM versions 18 through 21. Many OS distributions are shipping these versions, so it’s important to keep up with the times in order to make it easy to install C2Rust.

Inline assembly improvements

C2Rust translates inline assembly in C programs into Rust inline assembly. In some cases the transpiler was emitting invalid inline assembly that did not follow all of Rust’s ordering rules for arguments. This has been fixed and more inline assembly (including usage in ioquake3 and python2, some of the C projects we test against) should now transpile correctly. If your project uses inline assembly and you find any bugs in C2Rust’s translation thereof, please file an issue!

Installing

C2Rust v0.21 can be installed by running: cargo install c2rust --locked

Acknowledgements

Thanks so much to everyone in the C2Rust community who contributed to this release! See the release notes for a detailed contributor list.

This material is based upon work supported by the Defense Advanced Research Projects Agency (DARPA) under Agreement No. HR00112590133. Approved for public release; distribution is unlimited.