This guide explains how to apply SmartC2Rust to a C project of your own,
rather than one of the bundled benchmarks under benchmark/.
The translation procedure itself (Steps 1–6) is identical to the one in the main README. This document only describes what you need to prepare beforehand and how to adapt the example commands to arbitrary paths.
Prerequisites. You should have already completed the Setup with Docker
To translate your own project, you need to provide three things:
- A buildable C source tree.
- An entry-point specification (
targets.txt). - A test script that exercises the program and reports pass/fail.
Once these are in place, you run the same Steps 1–6 from the main README, substituting the benchmark paths with the paths to your own project.
Place your project under SmartC2Rust/program/. This is the standard
location for user projects. Keeping this convention means you can follow the Step 1–6
commands from the main README with only the project name changed.
/root/SmartC2Rust/
└── program/
└── myproj/ # One directory per project
├── src/ # Your C source tree (must build as-is)
│ ├── main.c
│ └── ...
├── targets.txt # Entry-point specification (you write this)
├── run_test.sh
└── base_test.sh # Original test script (you write this)
myproj is just a placeholder — use whatever name you like for your
project directory. Throughout the rest of this guide, replace myproj
with your actual project name.
SmartC2Rust relies on instrumentation and debug information embedded in the
compiled binary (used for capturing golden execution flows in Step 2 and
for static analysis in later steps). You must therefore build your project
with clang and a specific set of flags.
| Flag | Why it is needed |
|---|---|
clang (as CC) |
The instrumentation and DWARF handling assume clang. |
-finstrument-functions |
Inserts entry/exit hooks used to record execution flows. |
-g -gdwarf-4 |
Emits DWARF 4 debug info used by the analyzers. |
Edit your project's Makefile (or equivalent build configuration) so that
the compiler and flags above are applied to every translation unit.
c_build.sh is a build wrapper script that the pipeline invokes to build
your project. Its required side-effect is to produce a
compile_commands.json at the project root. The macro and C parsers
read this file to recover include paths and macro definitions for each
translation unit; the pipeline will fail without it.
How you produce compile_commands.json is up to your build system —
c_build.sh is just the entry point that runs the build.
Place the script at program/myproj/c_build.sh and make it executable
(chmod +x c_build.sh).
The script must accept an optional first argument:
c_build.sh init— clean any previous build artifacts, then build. Called once before the first translation run.c_build.sh(no argument) — incremental build. Called repeatedly during the repair loops.
After either form returns, compile_commands.json must exist at the
project root.
Pick the approach that matches your build system:
Make (or any build system without native support): wrap the build
with bear, which intercepts compiler
invocations and writes them to compile_commands.json.
#!/bin/bash
option=${1:-"build"}
if [ "$option" = "init" ]; then
make clean
fi
bear -- makeCMake: enable native export with -DCMAKE_EXPORT_COMPILE_COMMANDS=ON.
CMake writes compile_commands.json into the build directory, so symlink
or copy it to the project root.
#!/bin/bash
option=${1:-"build"}
if [ "$option" = "init" ]; then
rm -rf build
cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
fi
cmake --build build
ln -sf build/compile_commands.json compile_commands.jsonOther build systems: anything works, as long as the result is a
valid compile_commands.json at the project root. Some build tools
(e.g. Ninja via ninja -t compdb, Bazel via bazel-compdb) have their
own export mechanisms; otherwise fall back to wrapping with bear.
targets.txt lists the C functions that will be translated to Rust and
called from C via FFI. Each line has the form:
function_name:path/to/file.c:start_line:end_line
path/to/file.cis relative to your project root (the directory you pass as<c_source_dir>in Step 1).start_lineandend_lineare 1-indexed and inclusive, covering the function definition (signature through closing brace).
For command-line tools, the simplest choice is a single line pointing at
main. This matches what the bundled benchmarks do and lets the
minimize FFI strategy translate the program into idiomatic Rust without
any FFI boundary inside the translated portion.
main:src/main.c:42:178
For library-style code, list each public function you want translated. In
that case you will likely want "ffi_strategy": "preserve" in
config.json so the Rust side remains C-callable. See
docs/ffi-boundary.md for how the FFI boundary is
designed.
See
docs/reformat-testcases.md for details.
Two fields in config.json are worth thinking about for non-benchmark
projects:
"minimize"(default) — translate into idiomatic safe Rust. Best for command-line tools wheremainis the entry point and there is no external C consumer."preserve"— keep C-compatible signatures via FFI. Use when the translated functions must be callable from existing C code, e.g. when you are translating a library.
Below are the same six steps from the main README, with benchmark-specific
paths replaced by /root/SmartC2Rust/program/myproj. Adjust the paths to match your setup.
The numeric suffix _0000 is an iteration index used by the pipeline; it
will increment automatically across runs. The examples below assume the
first run.
cd /root/SmartC2Rust/macro
python3 pre_process.py \
/root/SmartC2Rust/program/myproj \
reformat base \
/root/SmartC2Rust/program/myproj/base_test.shProduces /root/SmartC2Rust/program/myproj/run_test.sh.
cd /root/SmartC2Rust/macro
python3 pre_process.py \
/root/SmartC2Rust/macro/trans_re_0000/myproj \
goldencd /root/SmartC2Rust/macro
python3 pre_process.py \
/root/SmartC2Rust/macro/trans_re_0000/myproj \
macro off \
/root/SmartC2Rust/macro/trans_re_0000/myproj/run_test.sh \
/root/SmartC2Rust/program/myproj/targets.txtcd /root/SmartC2Rust/trans
python3 pre_process.py \
/root/SmartC2Rust/macro/trans_c_0000/myproj \
meta \
/root/SmartC2Rust/program/myproj/targets.txt \
/root/SmartC2Rust/macro/metadata_0000/myproj \
/root/SmartC2Rust/macro/div_metadata_0000/myproj \
/root/SmartC2Rust/macro/trans_c_0000/myprojcd /root/SmartC2Rust/trans
python3 compile.py \
/root/SmartC2Rust/trans/c_code_0000/myproj \
/root/SmartC2Rust/trans/trans_c_0000/myproj \
/root/SmartC2Rust/program/myproj/targets_actual.txt \
trans \
/root/SmartC2Rust/trans/metadata_0000/myproj \
/root/SmartC2Rust/trans/div_metadata_0000/myproj \
database_0000/myproj/block_output.txt offcd /root/SmartC2Rust/trans
python3 semantics.py \
/root/SmartC2Rust/trans/workspace_0000_myproj/myproj \
s_repairThe translated Rust crate is at:
/root/SmartC2Rust/trans/workspace_s_repair_0000_myproj/myproj/trans_rust/