The C to Rust migration book
mainmatter.com
mainmatter.com
In contrast to what the C to Rust migration book is recommending (using FFI to integrate Rust with C), I've found it much easier to start from scratch. I recently finished a project where I rewrote Postgres in Rust[1]. For context, Postgres is about one million lines of C code.
On one attempt, I tried using c2rust to convert Postgres into unsafe Rust code. That attempt succeeded in terms of getting working "Rust" code, but any attempt to change any piece to safe rust, would require thousands of changes across codebase. Even though I had working Rust code, I found it infeasible to get to working idiomatic Rust code.
Instead what I found to be more effective was starting a new codebase and rewrite each file from the Postgres codebase into Rust one at a time. This allowed me to guarantee that at all times the new codebase was idiomatic and simultaneously I could make one pass over the Postgres codebase to get working idiomatic Rust.
YMMV, but I found it way easier to generate a whole new codebase from scratch rather than incrementally rewrite an existing codebase.
Starting from scratch also implies one major release where suddenly it's all Rust (so very risky). In contrast, we found that it's a much smoother transition for end-users and maintainers when we approach large codebases progressively, module by module, while the C engineers continue working on the C code and learn Rust in parallel.
Of course, each migration is different but I wanted to clarify with our field experience.
But LLMs are great at this, provided you have a test suite to keep the process on the rails.
Not really. The latency for an llm to make a single change is on the order of seconds. If in order to change a single function from unsafe Rust to safe rust requires thousands of changes, it will take hours to refactor a single function.
Strange. I was expecting the opposite move as LLMs know the C pitfalls and traps well enough to write corrected code.
Whether it would suit you, depends if you can learn to like Rust's approach of moving more work to the type system. In Rust you do certain things the Rust's way, period. Programmers used to C being unopinionated about everything find that objectionable.
But more directly, C barely lets you define non-NULL pointers. It doesn't have pointers that guarantee the data behind them is initialized, it doesn't have never-leaves-this-thread data types. Const merely guarantees that you can't (strongly shouldn't) mutate data, not that it definitely won't be mutated by any thread.
This is why I think a book is missing, people think C is much worse than it is because they do not understand what can be achieved.
Having a program potentially crash is very weak compared to eliminating the crashing code path entirely, guaranteed statically. This matters more and more as the programs grow, because series of 99% correct components will take correctness towards zero (failure probability mulitplies), while 100% correct scales.
Non-nullable types not groundbreaking. I'm just using this example, because it's basic enough that C has a relatable counterpart. Although some values are truly optional and Rust still has to deal with those, having &/&mut references that are never-ever NULL (and never dangling, never uninit, never misaligned, never unexpectedly mutated) makes this problem go away for all the code paths using them (which is the majority in practice).
But C completely lacks the real good stuff in Rust, like Send/Sync. You can shake some bugs with tsan, and have a static analyzer try to figure out effects and propagate them across functions, but like all analysis in C, it's hampered by lack of explicit info in the code (heuristics causing false positives), difficulty of performing true whole-program analysis, and dynamic code being a dead-end (unlike Rust where the traits are part of function types). Until you try it, you won't know how awesome it is to write thousands of lines of pervasively multi-threaded code, and have it work on the first try, without a single UAF or data race (and before you say what about deadlocks and higher-level logical races - there are library building blocks which usually prevent these too).
> people think C is much worse than it is because they do not understand what can be achieved.
I've been writing for ~25 years, from microcontrollers to compilers. Rust for 11. I think I have very good understanding of what is possible. C is stuck in 1970s. Rust at least advanced us to 1990's :)
I think it's much more common for people to not grasp how far Rust is ahead, and think it's merely a slight refinement of "be careful, use tooling" approach in C, rather than a shift towards a level correctness never seen in C.
Sure, compiler enforcement can help, but the advantages of it are massively exaggerated. The "stuck in the past" narrative I heard very often, with OO, with managed languages, etc. I would say that Rust goes in the wrong direction with monomorphization and with safe but too rigid and simplistic rules.
And the "never seen level of correctness". Just look at some of the bugs found in Rust, it is just the same old nonsense as everywhere. The favorite bug I use as an example is this: https://github.com/advisories/GHSA-5gmm-6m36-r7jh A language where you can get the trivial check for the dimensions of a matrix transpose wrong just like in C is not at another level of correctness. Or the famous cloudflare bug where some unhandled .unwrap brought the whole internet down. That you theoretically can prevent all this with the type system is great, but IMHO misses the point.
Enforcement is the point. Otherwise you're just saying people should be writing bug-free programs. "By construction" doesn't mean it's possible to construct a program satisfying a requirement, but that the compiler ensures it's impossible to violate it.
Even for a hypothetical perfectly diligent flawlessly executing programmer it's a time saver. If you want to run a 3rd party function across threads: in Rust, the compiler tells you if it's thread-safe or not, tracing through all dependencies, callbacks, checking data structures, mutexes, ref counts, etc.
And it works the other way, if I change a function that used to be thread-safe to do something unsynchronised, it won't compile any more in users' multi-threaded programs, instead of silently screwing them. Their code may have been perfectly correct before the change, and I couldn't feasibly track down everyone using my function before making a change, but having invariants in the type system automates this checking and communication.
> Just look at some of the bugs found in Rust, it is just the same old nonsense as everywhere
You're comparing your perfect self who writes flawless C on the first try with some random guy's library.
Properly designed Rust written by flawless Rust programmers is also bug-free.
Language safety is a human problem (exists to catch mistakes), and it's hard to sensibly argue whether your imaginary average C programmer is better than mine.
Rust's claims aren't absolute, so aren't disproven by a single counter-example (https://infosec.exchange/@littlealex/116838684458539473)
It's better to look at broader data, e.g. https://github.com/rust-fuzz/trophy-case - look how much lower typical severity is there.
And you can look at data from Google where they track drop in defect density in Android as it gets rewritten in Rust. I know all these top engineering companies just aren't as flawless as you are, and that's okay, Rust is for the rest of us.
I don't quite trust google's numbers and there are so many ways this could be biased. It is also not based on a comparison to modern C. One also has to realize how incredibly large the amount of C code is, which exists and with a lot also being very old and neglected. Any kind of CVE counting is highly misleading if you compare a new language with a security focus against existing projects. With more Rust code we will see more issues, especially when more average programmers use it that cut corners once they need to get things done. But I agree that this is where Rust can improve the situation, but so can modern C tooling. Wanting to put everything into the type system is a trend, but I don't think it is obviously the right step in the long run.
Sounds right up my alley. Thanks to you (and other siblings) for the thoughtful replies.
Coming from C I don’t think you’ll find Rust too foreign, once you internalize how the ownership rules work. In my experience the formal rules of Rust overlap a lot with behaviors that are good practice in C/C++ anyway, but there are some complicated concepts that you need to wrap your head around before expressing them in Rust becomes second nature.
Depends on platform for embedded. It not very pleasant to write rust if you have to think about binary size. For systems code - sure, use rust.
[the end]
and cheadergen, Mainmatter's tool for the reverse direction
So this is just an ad blogpost for the company.