Table of Contents
Fetching ...

C2SaferRust: Transforming C Projects into Safer Rust with NeuroSymbolic Techniques

Vikram Nitin, Rahul Krishna, Luiz Lemos do Valle, Baishakhi Ray

TL;DR

The paper tackles automating the translation of large real-world C projects to Rust while preserving safety and correctness. It introduces C2SaferRust, a neuro-symbolic pipeline that starts with C2Rust to produce unsafe Rust, then decomposes the code into translation units and uses an LLM to generate safer Rust for each slice, guided by static analysis and validated via end-to-end tests. A benchmark of 7 GNU Coreutils programs plus 10 Laertes benchmarks demonstrates substantial reductions in unsafe constructs (up to 38% in raw pointers, 28% in unsafe code) and favorable comparisons to prior methods. The work provides an open-source toolkit, static-analysis components, and a methodology for safely integrating LLM-generated translations, highlighting a practical path toward safer Rust porting of large C codebases with strong empirical support.

Abstract

In recent years, there has been a lot of interest in converting C code to Rust, to benefit from the memory and thread safety guarantees of Rust. C2Rust is a rule-based system that can automatically convert C code to functionally identical Rust, but the Rust code that it produces is non-idiomatic, i.e., makes extensive use of unsafe Rust, a subset of the language that doesn't have memory or thread safety guarantees. At the other end of the spectrum are LLMs, which produce idiomatic Rust code, but these have the potential to make mistakes and are constrained in the length of code they can process. In this paper, we present C2SaferRust, a novel approach to translate C to Rust that combines the strengths of C2Rust and LLMs. We first use C2Rust to convert C code to non-idiomatic, unsafe Rust. We then decompose the unsafe Rust code into slices that can be individually translated to safer Rust by an LLM. After processing each slice, we run end-to-end test cases to verify that the code still functions as expected. We also contribute a benchmark of 7 real-world programs, translated from C to unsafe Rust using C2Rust. Each of these programs also comes with end-to-end test cases. On this benchmark, we are able to reduce the number of raw pointers by up to 38%, and reduce the amount of unsafe code by up to 28%, indicating an increase in safety. The resulting programs still pass all test cases. C2SaferRust also shows convincing gains in performance against two previous techniques for making Rust code safer.

C2SaferRust: Transforming C Projects into Safer Rust with NeuroSymbolic Techniques

TL;DR

The paper tackles automating the translation of large real-world C projects to Rust while preserving safety and correctness. It introduces C2SaferRust, a neuro-symbolic pipeline that starts with C2Rust to produce unsafe Rust, then decomposes the code into translation units and uses an LLM to generate safer Rust for each slice, guided by static analysis and validated via end-to-end tests. A benchmark of 7 GNU Coreutils programs plus 10 Laertes benchmarks demonstrates substantial reductions in unsafe constructs (up to 38% in raw pointers, 28% in unsafe code) and favorable comparisons to prior methods. The work provides an open-source toolkit, static-analysis components, and a methodology for safely integrating LLM-generated translations, highlighting a practical path toward safer Rust porting of large C codebases with strong empirical support.

Abstract

In recent years, there has been a lot of interest in converting C code to Rust, to benefit from the memory and thread safety guarantees of Rust. C2Rust is a rule-based system that can automatically convert C code to functionally identical Rust, but the Rust code that it produces is non-idiomatic, i.e., makes extensive use of unsafe Rust, a subset of the language that doesn't have memory or thread safety guarantees. At the other end of the spectrum are LLMs, which produce idiomatic Rust code, but these have the potential to make mistakes and are constrained in the length of code they can process. In this paper, we present C2SaferRust, a novel approach to translate C to Rust that combines the strengths of C2Rust and LLMs. We first use C2Rust to convert C code to non-idiomatic, unsafe Rust. We then decompose the unsafe Rust code into slices that can be individually translated to safer Rust by an LLM. After processing each slice, we run end-to-end test cases to verify that the code still functions as expected. We also contribute a benchmark of 7 real-world programs, translated from C to unsafe Rust using C2Rust. Each of these programs also comes with end-to-end test cases. On this benchmark, we are able to reduce the number of raw pointers by up to 38%, and reduce the amount of unsafe code by up to 28%, indicating an increase in safety. The resulting programs still pass all test cases. C2SaferRust also shows convincing gains in performance against two previous techniques for making Rust code safer.
Paper Structure (29 sections, 10 figures, 12 tables, 1 algorithm)

This paper contains 29 sections, 10 figures, 12 tables, 1 algorithm.

Figures (10)

  • Figure 1: An overview of our system, C2SaferRust, to translate a C program to safer Rust. We first run the C2Rust transpiler to get non-idiomatic Rust, then decompose the program into chunks. For each chunk, we slice the program to extract relevant context information and dataflow information, and compose this into a translation prompt for an LLM. The LLM produces safer Rust, which we swap back in and test end-to-end. If the program doesn't compile or pass test cases, we give the LLM feedback and repeat.
  • Figure 2: An example of the output of our decomposition algorithm as described in \ref{['sec:decomposition']}, applied to the cat function from the cat program. Translation Unit 1 is the "root" unit that represents the function as a whole, and all other units are nested within it. The units are annotated with comments showing live-in and live-out information, which is described in \ref{['sec:slicing']}.
  • Figure 3: A portion of the callgraph of cat. Each node denotes a function, and the edges denote calls. The functions are sorted in a reverse topological order. The numbers next to each function denote their positional index in the ordering of translation units. main_0 and cat are associated with a range of indices (4-8 and 16-29) because they have multiple translation units within them.
  • Figure 4: Translating units with an LLM. For the root translation unit (left), we generate the translation of the function body plus all of its call sites simultaneously. For the inner translation unit (right), we translate just the code contained within it. In both cases, we also allow the model to specify any imports it needs. Full prompts are in the Appendix.
  • Figure 5: Original C implementation
  • ...and 5 more figures