| Crates.io | citrus |
| lib.rs | citrus |
| version | 0.10.2 |
| created_at | 2017-08-19 12:21:25.052762+00 |
| updated_at | 2023-10-22 11:21:51.817765+00 |
| description | C to Rust syntax converter |
| homepage | https://gitlab.com/citrus-rs/citrus |
| repository | https://gitlab.com/citrus-rs/citrus |
| max_upload_size | |
| id | 28103 |
| size | 193,303 |
This is a tool that helps convert C programs to Rust programs. It transforms C syntax to Rust syntax, but mostly ignores details of C semantics.
The generated programs may not run, and may even not compile. However, the tool produces readable source code that can be manually refactored into a Rust program.
void gz_compress(FILE *in, gzFile out) {
char buf[BUFLEN];
int len;
int err;
for (;;) {
len = fread(buf, 1, sizeof(buf), in);
if (ferror(in)) {
perror("fread");
exit(1);
}
if (len == 0) break;
if (gzwrite(out, buf, (unsigned)len) != len) error(gzerror(out, &err));
}
fclose(in);
if (gzclose(out) != Z_OK) error("failed gzclose");
}
#[no_mangle]
pub unsafe extern "C" fn gz_compress(mut in_: *mut FILE, mut out: gzFile) {
let mut buf: [i8; 16384];
let mut len;
let mut err;
loop {
len = fread(buf, 1, std::mem::size_of_val(&buf), in_);
if ferror(in_) != 0 { perror("fread"); exit(1); }
if len == 0 { break ; }
if gzwrite(out, buf, len as c_uint) != len {
error(gzerror(out, &mut err));
};
}
fclose(in_);
if gzclose(out) != Z_OK { error("failed gzclose"); };
}
See releases for binary downloads.
Requires clang to be installed. On macOS requires Xcode.
Converts one file at a time, prints to stdout:
citrus [<citrus options…>] <file.c> [<compiler args…>]
Options are:
--api=rust — Allow Rust-only types in function arguments and don't export functions to C. Use this if you're porting all code at once.--api=c — Generate all function arguments for C interoperability. Use this if you're porting code function by function.Compiler args are standard flags required to compile the C file, such as -I<include dir> and -D<macro>.
citrus program.c -I./include
citrus --api=rust program.c > program.rs
The typical workflow is:
C is very weird from Rust perspective. The generated code will be very un-Rust-like. Please don't judge Rust by this :)
Use size_t for all lenghts and array indexing (or ssize_t/ptrdiff_t if you need negative values). Rust is super picky about this.
Change as much as you can to const: variables, function arguments, globals. In Rust things are immutable by default.
i reused throughout a function, (re)define it for each loop.Minimize use of macros. Non-trivial macros are expanded during conversion and their high-level syntax is lost.
inline functions. For conversion you may even want to undefine assert, MAX, and offsetof.func_int, func_float), keep just one version with a unique typedef for the type. You'll be able to replace the typedefed name with a generic parameter later.Replace int and long with types of specific size, such as <stdint.h>'s int32_t, int64_t or size_t.
bool from <stdbool.h>signed char only when you really mean to use it for negative numbers.unsigned char, short, float, double and long long can be left as-is.In function arguments use arr[] for arrays, and *ptr for exactly one element.
arr[i] yes, ptr+i no).f(size_t length, arr[static length]) (yes, it's a valid C syntax).Add __attribute__((nonnull)) to functions that should not be called with NULL arguments.
Change for loops to be in format for(size_t i = start; i < end; i++).
while instead (but avoid do..while).Don't use var++ in expressions. Use ++var or put it on its own line. Rust only allows var += 1;
Remove all goto and its labels.
Remove "clever" micro-optimizations. They are really painful to convert, and most end up being not applicable.
Vec.Having tests helps a lot. Not only unit tests, but also a high-level test such as a known-good whole program output.
use std::os::raw::*; use std::slice; use std::ptr;static) and use bindgen to generate bindings for calling back to C.pub.Vec. Rust's fixed-size arrays are PITA.let foo = slice::from_raw_parts(foo_ptr, number_of_elements_not_bytes).Because if the C3 dependency it requires exactly LLVM 5.0 and a corresponding static Clang library (libclang.a + headers). You may need to build Clang from source for this (sorry). The stable C API of clang is not sufficient for the task, so Citrus has to use a fragile C++ clang API, which is not guaranteed to be compatible with anything.
Build LLVM 5 and static Clang from source. See more detailed build instructions. Set variables to LLVM and Clang locations:
# Must have 'libclang.a'
export LIBCLANG_STATIC_PATH=…/clang/build/lib/
# Path straight to the 'llvm-config' executable
export LLVM_CONFIG_PATH=…/llvm/bin/llvm-config
# Should contain 'clang' and 'clang-c' sub-directories
export LIBCLANG_INCLUDE_PATH=…/clang/include
cargo build