He doesn't appear to be a direct contributor, probably more of an inspiration. The only overlap I know of is that he also works/worked at Galois.
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.
> The output of c2rust looks more like a literal projection from C into Rust, Corrode tried to limit the use of unsafe.
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.
We've only started pondering what subset of C++ we might be able to support in future work for the project. Currently only C is in scope. That's an obvious next goal but it seems quite a bit more daunting!
Fortunately, once you start refactoring the Rust to take advantage of the functionality that doesn't exist in C (like slices, iterators, etc), things start to look much cleaner.
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.
> Sounds like a case of "writing C in Rust" compared with "writing Rust".
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.
> 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.
A translator like this is still really useful as the first step of transforming a C codebase into a Rust codebase. The important thing is to get working code first, and then you can modify the code to be more idiomatic.
Yeah, I assumed that this was just converting word-for-word as a first step and one was expected to then go back to use Rust idioms to tweak the code if necessary. Definitely useful if you want to move code-bases to Rust entirely.
That's right. The translator just tries to preserve the original code. Refactoring things into proper, idiomatic Rust is the next step. The hope is that it will be easier to get the translation correct transforming unsafe Rust to safe Rust than having to go from C to safe Rust.
The compilation target could be a Rust slab [0], much like Emscripten uses Typed Arrays [1]. In doing so, it would retain much of the safety properties of Rust, but it pushes the problem into maintaining "the heap" for the C code.
Yes, you can call any C using the Rust FFI but then you need to create a safe wrapper around the unsafe Rust calls to C. This tool looks like it's for generating a first pass at porting existing C code to Rust. Once you have that C-to-Rust conversion, it becomes much easier because you can incrementally port select types instead of incrementally porting the API and dealing with the impedance mismatch between the Rust api and C api (you can work on function arguments instead of function calls).
I think the next step would be to have a tool that can convert C into safe Rust with a combination of static analysis and framework/program-specific user-written rules to translate specific C framework constructs into Rust equivalents.
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.
There's a real sense in which, if this were true, we might not need Rust. If we could mechanically translate the Linux kernel to safe Rust, we could prove the Linux kernel safe. If we could prove the Linux kernel safe, that would be a strong argument against a need to translate it to Rust.
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.
The major difference would be that future versions would be in safe Rust as well. All code written after that point would be safe.
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.
If we had a standard for safe C that the linux kernel could feasibly meet, then it could be a condition of future changes that it continue to be safe.
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.
As I see it the main benefits of C over using other languages like Rust, are generally the ease of getting access to raw memory, sharing pointers, and direct access to hardware. Rust is actually good at all of that too, but is just as unsafe.
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.
Removal of undefined behavior is an explicit goal of Zig, in fact, and I see Zig as a stronger target for automatic C conversion since the semantics are intentionally closer to C. Still possible to trivially footgun, but the compiler tries to catch more edge cases.
There's definitely a space for code that can't be borrow checked, at any rate.
That assumes that such a translation is either a binary success/failure, runs at once, and runs for the entire kernel.
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.
That’s almost certainly impossible, but I bet there would be some value in doing the conversion without wrapping anything in unsafe blocks and letting compiler warnings guide through making it safe.
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.
Wouldn't it be simpler to do a C-to-Rust translator that produces Rust code that does not actually have to compile?
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 is compiling C into very unsafe Rust as a target language:
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 problem is the type noise, which may or may not be avoidable. Most of the mess you see there is probably better expressed as std::mem::* function calls which give proper names to the operations (you can see one of them, .offset, used here but it's a very general function). I don't think it's a good idea to try to convert C code like this to more complex std::mem calls automatically but once you have the ugly Rust code, it becomes relatively easy to start porting it to be more Rustic.
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.
Even though it's translating to unsafe Rust, it does still have some value. In particular, C libraries can be converted into Rust libraries that can be managed via Cargo, avoiding the need to deal with C tooling.
That means anyone that wants to use a Rust crate that depends on a C library has to somehow install the C library. It's possible that's easy through a package manager, or maybe it's not, and you have to build the C library, and now you have to deal with C tooling.
> This is compiling C into very unsafe Rust as a target language
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).
The premise here must be that the translator (human) has the original C code. So answering questions like "is this an array" is as simple as reading the C code to determine 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.
So answering questions like "is this an array" is as simple as reading the C code to determine that.
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.
I know and agree that unsafe Rust is problematic, but that criticism of this is missing the forest for the trees. The original code is just as unsafe.
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.
I think it is you missing the forest for the trees. The majority of C code does not in fact have unsafe memory accesses. So clearly we have a problem here with the translator being too stupid or the language; neither is something I look for in a tool I use.
No, all memory accesses in C are unsafe in the keyword-in-Rust sense: `unsafe` indicates that an API can be misused to cause undefined behaviour, contrasting to non-`unsafe` code where any potential undefined behaviours will be caught at compile time or runtime, no matter how incorrect the program/use of the API is. Just like C, it is an incorrect Rust program if undefined behaviour actually happens, meaning all uses of `unsafe` have to actually be safe, but it's up to the programmer to ensure this, with less compiler assistance than in normal Rust.
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.
(It would have been clearer if my original comment said: "The C and the Rust both risk undefined behaviour in the same ways", instead of using an overloaded meaning of unsafe in saying that the C is unsafe.)
In the context of Rust, unsafe does not mean incorrect or vulnerable to memory unsafety. Unsafe refers to code whose memory safety has not been proven (by the compiler's static analysis based on ownership and lifetimes, given the assumption of correctness of other unsafe code (so you can look at the proof of memory safety of a program as a whole as an inductive tree of proofs, with the leaves being unsafe code like in `std::Vec`, which is subject to formal and practical scrutiny outside of rustc's static analysis).
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.
While the translated code is harder to read, it's much easier to refactor into compiler-checked safe code than the original C. I think doing the translation as part of a larger migration from C to safe Rust is essentially the only defensible reason to do it, as it doesn't seem like other reasons can justify the risk of (as one of the sibling comments points out) bugs in the translator and the code being unreadable, aka, in an "unnatural" representation of trying to express C idioms in Rust.
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.
Your first point is of course true, but most statements about the behaviour of code needs to have "assuming no bugs" appended to it, so I'm not sure that it's a particularly interesting point. A more precise interpretation might be that the translator is likely to have more bugs than other components in the pipeline (the compilers and linkers etc.), since it is unlikely to be used nearly as much as them; I wonder if one could do things like compile with rustc and clang and somehow compare the LLVM IR for equivalence (but... I suspect that the code generation patterns for each aren't similar enough).
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.
> I wonder if one could do things like compile with rustc and clang and somehow compare the LLVM IR for equivalence
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.
#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;}
There are definitely ways to overwhelm the translator web demo. I know I'm not clever enough to block all of them. I put the page up to give people a way to try out the translator without having to build it. Please don't kill it :-)
If you try making p a local array of length 10, ordinary Rust subscripting comes out. So the translator knows what to do with arrays; it just makes very C-ish assumptions about them. It just needs to know when a pointer is really an array to treat it as an array.
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.
78 comments
[ 3.3 ms ] story [ 145 ms ] threadc2rust 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.
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:
Although unless it's really necessary I would expect the simpler version: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.
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.
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:
"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
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.
https://github.com/immunant/c2rust/tree/master/cross-checks
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.
Fuck rust.