Crates.io | citrus |
lib.rs | citrus |
version | 0.10.2 |
source | src |
created_at | 2017-08-19 12:21:25.052762 |
updated_at | 2023-10-22 11:21:51.817765 |
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