C2Rust: translate C into Rust code
c2rust.com
c2rust.com
pub unsafe extern "C" fn insertion_sort(n: libc::c_int, p: *mut libc::c_int)
-> () {
let mut i: libc::c_int = 1i32;
while i < n {
let tmp: libc::c_int = *p.offset(i as isize);
let mut j: libc::c_int = i;
while j > 0i32 && *p.offset((j - 1i32) as isize) > tmp {
*p.offset(j as isize) = *p.offset((j - 1i32) as isize);
j -= 1
}
*p.offset(j as isize) = tmp;
i += 1
};
}
The output is unmaintainable, like the output from a compiler. This isn't translation into usable Rust.A C++ to Rust translator would be a good thing, but it has to be much smarter than this. This doesn't even comprehend arrays in C.
A good translator might need some human help to deal with C's ambiguities. Like "Is this an array? (Y or N)" How big is it? (Enter expression). Then you get a program that's real Rust. If the user is wrong about the size, safe Rust will either allocate too much space or get a subscript error.
The bigger issue I see here is that this should be implemented as an iterator. I don't think there's any hope of automatically converting to those though - there's just no C equivalent except pointer arithmetic.
And you can write oracle tests to catch those.
That's still pretty bad, especially for low-level legacy libraries that aren't supposed to break userspace under any circumstances.
It works pretty cleanly, but it is true that one toolchain is easier than two.
Especially on Windows, this can be a pain.
It’s basically as safe as the original.
> This isn't translation into usable Rust
It’s slightly more usable, and easier, than FFI to raw C (see other comments about Cargo integration).
I’ve been thinking about language rewrites like this, and generally I’m really torn. If it’s the intention to get rid of your C, it might in fact be easier to use bindgen for the C header translation, and create decent Rustful interfaces that abstract the C. After that maybe use this tool as a starting point to get Rust or possibly just rewrite the section by hand. In either case you’ll be rewriting the logic into safe Rust at some point, otherwise I don’t see a huge point in doing this (other than the Cargo integration, though there are other options for that).
Allocating too much space or getting a subscript error seems preferable to unsafety, and something one can hammer out with oracle testing.
Actually, it would be cool if there were generated quickcheck-like oracle testcases alongside this so you could start your translation to safe rust and test along the way.
If only. Here's the original function definition:
void insertion_sort(int const n, int * const p)
"p" is really an array. But C doesn't know that. Rust needs to know.Around 2010, I proposed an extension to C [1] in which you'd write that as
void insertion_sort (int const n, int& p[n])
Instead of a pointer, you'd use a reference to an array. Same calling sequence - the compiler passes a pointer. But with enough info that the callee knows the array size. Rust needs that info to do its job. Somehow, you have to get that info into a C to Rust translator.[1] http://www.animats.com/papers/languages/safearraysforc43.pdf
Refactoring from unsafe Rust into safe Rust manually is likely to be easier than directly from C into Rust: one is only trying to deal with one small delta (like changing length/pointer arguments into a slice) rather than also having to deal with syntax changes and tooling issues.
As others have said, it is essentially impossible to automatically translate to safe Rust without non-trivial annotation effort, since C the language and C in practice is very different to how Rust guarantees safety. It seems to me that that "annotation" effort is probably better spent going unsafe to safe Rust (where, subjectively, the language is nicer to use, like better enums and less error prone control flow) rather than annotating the original C.
Every single pointer access `*p` or `p[i]` in C has potential for undefined behaviour (e.g. p is NULL, points to an invalid object, or i is out of bounds), and thus the Rust equivalent is surfacing this risk in a more obvious way.
By this definition, all C code has unsafe memory accesses. However, it is possible to define static analyses that can prove memory safety of some portions of C programs, and incorporating such a thing into C2Rust would allow it to generate more safe Rust and less unsafe Rust.
But the translated code being unreadable to humans adds a whole new layer of "unsafe".
That is to say, I don't think humans should be reading or maintaining this code for more than it takes to refactor to be safe(r). (Whether this is how this sort of tool is used in practice is a different question, and one that definitely needs to be considered.)
That assumes zero translation errors as well as the original C code not relying on a particular compiler's specific implementation of unspecified behavior.
As for unspecified behaviour... that's true! Again I wonder if clang is a useful component here: I suspect if C code behaves correctly with clang, then the translation to Rust compiled with rustc probably works too, both because the translator uses clang, and because both compilers use LLVM.
The point (in contrast to the usual complaint of Animats) I was trying to convey, but was nitpicked was: a correct translation from fully-specified C to unsafe Rust doesn't add any unsafety. Whether this applies in practice is slightly different, but there's questions about whether pretty much any statement about C code applies in practice: since undefined behaviour is so rife and easy to trigger, many/most programs are outside the specification (fuzzing C programs often results in segfaults---undefined behaviour---and so the program is outside the bounds of C).
Wrapping up, I'll emphasise that the value in this sort of translation is as a stepping stone towards safe Rust, not as a final `unsafe` product.
The approach we've taken so far with C2Rust is to build compiler plugins that instrument C and Rust code so we can dynamically check their behavior when fed the same input.
An eventual goal could be for instance to automatically translate the Linux kernel with the aid of a lot of custom rules to handle its constructs.
Note that this point is independent of the question of whether rewriting the Linux kernel in Rust is actually good/feasible idea. I also think that Rust has other advantages over C that aren't just safety, so it's not a complete comparison--safety is just the biggest one.
What you say is true for a single point in time, not for the long-term future.
But, I would say this is probably infeasible, so theorizing too much about it seems a little wasteful. If the Linux maintainers, Linus et al, suddennly decided to convert to Rust, it would probably done incrementally, module by module. But it’s unlikely to happen given past statements.
As you say, it's pretty hypothetical on both fronts--we're not gonna be able to prove that about a C project like the kernel, and they're not gonna rewrite in Rust any time soon.
The point I’m making is that the mythical “safe C”, would have to remove many of the benefits of why people enjoy C, so why not just use Rust at that point?
One variation I’ve seen is the idea of “safer C” put forward by DJB, which would remove all undefined behavior as it’s main goal.
In any case, people would need to learn something new, and that seems to be a huge barrier for any language.
There's definitely a space for code that can't be borrow checked, at any rate.
More likely, such a tool will be used file-by-file and module-by-module, and when it fails, it will either report obvious mistakes (that will then be fixed in the C source, in the same way that e.g. people doing Python 2 to 3 conversions end up fixing bugs in their Python 2 code to prepare it for reliable conversion) or report more complex design mistakes that warrant rethinking an approach from scratch and starting the new implementation in Rust.
If that's what happens, then we would not be proving the Linux kernel safe, and we would have a strong argument for having done the translation.
Refactoring C to Rust code is hard. It's not enough to derive bits of information function by function. Often you need to re-architect the whole approach, which is a Sufficiently Smart Compiler problem. Until I've tried it I did not fully realize just how much C uses pointers. Pointer soup is everywhere (and hardly any explicit lengths anywhere).
In C's type system the concept of ownership doesn't exist. From that angle it's "dynamically typed". Converting "any pointer can have any ownership, it's implicit from usage" to Rust's "all ownership is statically known" is as hard as converting JavaScript's use of types to a statically-typed language.
Basically one-to-one but with errors in Rust because it's unsafe, and then let the programmers deal with the error codes. That still sounds like a valuable tool since it would take some parts of the boilerplate out of the equation.
This has potential. If you had some way to annotate or advise the translator, it could do a much better job, and turn out safe, usable Rust.
Some of that can be done automatically by looking at calls to functions. If it's always an array going in, the formal parameter can become an array. Any C that doesn't have explicit pointer arithmetic should be translated into Rust that doesn't have explicit pointer arithmetic.
c2rust is written mostly in Rust, Python and C where as corrode was written in Haskell. The output of c2rust looks more like a literal projection from C into Rust, Corrode tried to limit the use of unsafe.
$ tokei
-------------------------------------------------------------------------------
Language Files Lines Code Comments Blanks
-------------------------------------------------------------------------------
Autoconf 4 199 85 56 58
C 88 6989 5175 754 1060
C Header 21 2949 1497 1073 379
CMake 5 84 60 10 14
C++ 5 3015 2320 295 400
C++ Header 1 132 101 7 24
Dockerfile 1 62 50 6 6
HTML 2 84 78 0 6
Makefile 3 88 63 3 22
Markdown 23 1482 1482 0 0
Python 26 6597 4988 319 1290
Rust 250 45111 35949 2893 6269
Shell 44 697 429 141 127
Plain Text 15 555 555 0 0
TOML 11 197 148 24 25
Vim Script 1 233 195 4 34
-------------------------------------------------------------------------------
Total 500 68474 53175 5585 9714
-------------------------------------------------------------------------------I don't think either statement is true. Both projects bill themselves as semantics-preserving translators, as in they try to generate Rust code which compiles and works the same way the original C code did (in fact corrode has a cc script which first compiles C to Rust then Rust to native).
Citrus is the literal projection: it's not semantics-preserving and usually doesn't generate compilable code.
Our original plan was to simply work to improve Corrode. We eventually decided to to implement a new tool that uses Clang as the frontend in order to get more reliable parsing, preprocessing, and type checking of the input C code. The result is that we are able to support more code and C extensions than we thought we'd be able to building on top of the good work of the Corrode project.
Edit: I think it was https://github.com/maidsafe/crust .
Translating the code into ugly, unsafe Rust is intended to only be the first step. We're working on tools to help with that refactoring process, too.
Duh? It's literally taking C code and generating Rust code which behaves the same.
> You're not really writing in a language until you're thinking in its idioms.
The entire point is to get your foot in the door.
This gives you a pile of Rust code which (bugs aside) should behave the exact same way C code does.
From there on you're living in the Rust world and can improve that as you see fit.
If you can afford doing the initial transition in one short, later improvements are much simpler than having to maintain an internal (moving) front between remaining C code and new Rust code as e.g. librsvg does.
That's all well and good... if you don't have to maintain the project while doing the necessary C-as-Rust -> "proper" Rust conversion. This C-as-Rust output seems[1] like it woul d be incredibly difficult to do bugfixes in and there's also the issue of what happens if the C code happened to rely on implementation-defined behavior (or even UB which the implementation happens to "do the right thing" with).
Unless you have a trivial amount of code, I'd say the right thing is to do the conversion in small chunks. This also gives you a much better way to ensure (through test suites) that you're not introducing excessively many new bugs when rewriting C-as-Rust to "proper" Rust.
[1] I realize this is early days, but I'm talking about the present time.
It's also explicitly typing everything, which increases verbosity.
The "clean" rust conversion would look something like this:
fn insertion_sort<T: Copy + Ord>(p: &mut [T]) {
for i in 1..p.len() {
let x = p[i];
let mut j = i;
while j > 0 && p[j-1] > x {
p[j] = p[j-1];
j -= 1;
}
p[j] = x;
}
}
Although unless it's really necessary I would expect the simpler version: fn insertion_sort<T: Ord>(p: &mut [T]) {
for i in 0..p.len() {
for j in (0..i).rev() {
if p[j] < p[j + 1] {
break
}
p.swap(j, j + 1);
}
}
}It's semantics-preserving, and C constructs are not expected to match safe Rust, so that makes sense, Corrode does more or less the same.
Citrus generates "safe rust", it also doesn't generate working code.
[0] https://github.com/carllerche/slab
[1] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Type...
It allows unsafe C to be incorporated into safe Rust while maintaining the same level of safety.
A similar less ergonomic solution would be to load C via WASM modules.
I'd take slab or wasm over whole lib translation into unsafe Rust.
#define a "xxxxxxxxxxx"
#define b a a a a a a a
#define c b b b b b b b
#define d c c c c c c c
#define e d d d d d d d
#define f e e e e e e e
#define g f f f f f f f
#define h g g g g g g g
#define i h h h h h h h
#define j i i i i i i i
main(){char*z=j;}
This fails.
The error is entity not found.