I've thought alot about this before. Rust doesn't really have very great interoperability with C - most languages really don't, because the API for C programs revolves around functions, structs, macros, etc. Most languages can only really get part of that, and then the rest gets essentially a separate implementation in the language you're porting too - leading to a certain amount of manual duplication and intervention necessary (We don't call it porting when you use a C library in C). Design goals Rust has made compound the issue a bit more: For example, Rust uses the C++ ABI, not C. Rust can't generally use header files directly. The Rust<->C conversion loses a lot of information and can result in fairly messy code by Rust standards, etc.
This makes combining Rust into already existing C code a hassle, as it is generally worse then even just trying to use C and C++ together which is already fairly annoying and error-prone. This is compounded by Rust's safety goals which generally require a different design to a problem then the "C-style" approach would be - IE. You're probably not going to get good Rust code by simply replacing a .c file with a Rust file, because the entire time you're going to have to make use of the unsafe C functions that your program includes.
Perhaps the bottom line is that (for good reason) Rust is much more then just an improved C. And because of that, combining C and Rust is always going to feel like combining two separate systems together with a compatibility layer in-between - because that's really what it is - and that's just not very attractive.
I know little about Zig (I'm reading about it for the first time now) and don't at all predict it to replace C. That said, I would love a language which has a focus on maintaining very good compatibility with C while fixing the various pieces of its design that are fixable within the bounds of the language. For example, I noticed it mentioned in the README that in Zig that pointers are nonnull by default, and can be made nullable by adding the 'maybe' attribute. This is a feature I would love in C, but really isn't there (gcc has a nonnull attribute, but it is really essentially useless and nothing like you'd want). Essentially, I would like a language where it feels like writing code for the same system, but in a different (better) syntax - where I can drop it into a project and it works together with the system like any other .c file (Though with a different compiler, obviously), but the code inside is much better then what you can do in standard C. I think that such a language could retain the reasons why people (like me) still like C, while fixing a lot of the uglier sides to the language that everybody is aware of but aren't going to get fixed any time soon. And perhaps most importantly, I think such a language is definitely possible (Though it may not be able to employ all the features you may want) - but I don't have the time nor probably the skills to really do it well besides list off the things it should fix and shouldn't fix.
As for starting a project though, if you ignore difficulty to learn then I would agree with you: There's probably little reason not to just go for Rust if you're already willing to learn a new language. For most projects the issues I outlined above don't really matter assuming the entire project is written in Rust - besides the want for a language closer to C in design. That said, I readily concede that I have no idea if Rust's borrow-checking semantics could ever work without the extra features they added that take the design away from being 'C-like' - and considering that's one of the definitive features of Rust, is easily worth losing the 'C-like' detail if it is necessary.
But when considering a project like the Linux Kernel, the GNU coreutils, git, GTK, and other various large projects where conversion to Rust is probably impossible without a complete rewrite, being able to use a 'better' C while still retaining the aspects that people like would probably be a nice step in the right direction. The chances of it happening are nil, but it would still be a step in the right direction.
Rust uses its own ABI, not any of the various C++ ones.
Our strategy for interop with C is two-fold: first, a very thin, direct wrapper. These are the various *-sys packages. They know how to link in (and maybe even build!) the underlying C library, and provide functions you can call from Rust. Then, on top, people can write a more idiomatic Rust wrapper, working in Rust's safety guarantees.
That takes some work and time to get right, but hopefully, it means that in the end, Rust users of C libraries shouldn't have to deal with the stuff you're talking about.
That strategy is also used by Haskell, what typically happens is that the idiomatic wrapper ends up being out of date, poorly documented and if the library is prominent enough there might be even several competing ones.
You're 100% right. I was actually thinking of name-mangling when typing that, but that's still not quite right. The point I was trying (and failing) to make is that Rust uses things like name-mangling and a different ABI that makes interop more of a challenge - similar in challenge to interop with C++ from C.
C technically has name-mangling too, but generally speaking either there is no mangling at all, or they just add an '_' to every symbol name.
Edit: To address your second point, I think that is a step in the right direction - and I think that Rust's compatibility with C libraries is probably fine, and comparable to most languages. I don't consider that to be so big of a turn-off that I wouldn't want to use Rust for a new project, though obvious writing wrappers isn't always fun or error-free.
But, assuming I'm understanding what you're getting at correctly, I'm not sure that will really solve the core problem I'm trying to get at: Generally speaking, nobody wants to be maintaining compatibility wrappers for APIs that exist entirely within their program and are probably changing all the time, and that's really what you need if you want to replacing part of a C program with Rust.
For example, to take it to the 'extreme' - the Linux Kernel module API changes virtually every version of the kernel (And the ABI is not guaranteed at all), and maintaining a complete Rust wrapper for it would not be a fun time even if a certain amount of it can be auto-generated. The API includes a very complicated mess of functions, inline functions, macros, structures (with varying different types of alignment and padding). None of it is guaranteed to stay the same across versions. And I think it is fair to say that most C programs have internal APIs like this (Though not as crazy) that are changing all the time and not intended to be seen by the 'outside world'. It's these types of things that I see being a problem for interfacing with Rust - APIs that are changing all the time in complicated ways which make writing and maintaining a wrapper very annoying and error-prone.
Oh, and to reply to your edit: yes, writing a wrapper for an unstable interface isn't exactly fun. But that also means that your C code is going to have to update with each release too, an unstable API is unstable for everyone.
A C API can stay relatively the same if you switch a function to a macro, or make it inline. In a lot of cases you don't actually care which of the options it might be to begin with - the syntax generally stays the same, and the situations where you care (Mostly just function pointers) are somewhat uncommon. Such changes would easily break a simple wrapper though. That said I do see your point - It's not like the C code is guaranteed to work either, so perhaps that is acceptable. The maintainer still has to weigh the disadvantages of supporting a Rust wrapper to the advantages of allowing Rust code.
My original point (which has gotten a bit muddled in the details) was just that there could be room for a language closer to C that offers to fix some of the more annoying issues, while still keeping very good compatibility with C overall and avoiding the need for 'wrappers' and such to interface it with C code.
> But when considering a project like the Linux Kernel, the GNU coreutils, git, GTK, and other various large projects where conversion to Rust is probably impossible without a complete rewrite
Firefox is shipping Rust code right now. There are various examples of Linux kernel modules written in Rust. There are rewrites of the coreutils in Rust. I don't understand why you claim this.
It is, and that is cool. That said, what it's shipping is essentially a separate library written in Rust that exposes a C API for parsing mp4s - it replaced one libraries usage with another, with a presumably sable API consisting of nothing but C struct's and C functions. Perhaps a key to point out is that it doesn't actually interface with Firefox very much at all - it isn't capable of accessing any Firefox state for instance. I also didn't list Firefox as you'll note - it's not a C project, it's a mishmash of languages already (Mostly JS and C++ from my understanding). I'm really talking about projects that are currently C-only adding in Rust code to the mix - they are structured much differently then a project like Firefox and the interfaces are generally much more complex as far as C goes.
I will add though, servo is making pretty impressive efforts. It is again a complete rewrite though, not an integration of Rust into Gecko which is the type of thing I'm talking about. A rewrite of a library in Rust against a stable API is generally possible, depending on the API - though the amount of work may make it prohibitive to get something usable.
> There are various examples of Linux kernel modules written in Rust.
I would like to see an example of a legitimate Linux Kernel module written in Rust that is in some form of use. I have never seen one besides a toy implementation and I can all but guarantee you it doesn't exist, for the reason that it would be way to much of a hassle to attempt to get it working with the mixture of macros, inline assembly, inline functions, gcc attributes, etc.
> There are rewrites of the coreutils in Rust
Coreutils is admittedly not a very good example, if only because coreutils isn't actually that big/complex of a project (Though supporting all the GNU flags and arguments is a pretty big task). They're also mostly just separate exe's anyway, so you could replace one or two with Rust code without much of a difference - I'd gladly remove Coreutils from the list if you would like.
> That said, what it's shipping is essentially a separate library written in Rust that exposes a C API for parsing mp4s - it replaced one libraries usage with another, with a presumably sable API consisting of nothing but C struct's and C functions.
This is true.
However, if we put aside the Rust code that is actually shipping, there's still plenty of work going on in sharing Servo's code with Gecko's.
One example of this is the ongoing experimental work to move Servo's style system into Gecko. Servo's style system doesn't have much of an "API surface"; everything is a surface. There's plenty of reaching in and grabbing Firefox state and vice versa.
Some of this is just done directly by reading or writing to structs. Some of this is done by writing small wrapper C functions (https://dxr.mozilla.org/mozilla-central/source/layout/style/...) and using bindgen. Bindgen has C++ method/ctor/dtor generation abilities that would obviate almost all of these manual bindings, though we aren't using them yet[1].
Overall, mixing C++ and Rust at a rough API surface in a large codebase hasn't been that hard. It's not easy either, but it's doable and I don't think it's anywhere close to being "nearly impossible without a complete rewrite".
[1]: The reason behind this has to do with name mangling -- Rust doesn't understand C++ name mangling, so bindgen generates the mangled function names and wraps them in a nicer API. This is all great, except that it means that the generated bindings stop being cross-platform, and you need to twiddle with the build system to either dynamically generate them or have a way to statically generate them for all platforms. Right now the servo-gecko integration uses a temporary build system that will be replaced soon, so this hasn't been a priority.
Rust supports all of these features. So your complaint is that nobody has written a translator from these things to Rust yet. That's a matter of filing PRs against bindgen, not some basic problem with the language.
If you think it is so trivial, then why hasn't it been done yet?
Rust macros can't do all the same things that C #define macros can. You can't insert inline C code into Rust code without some serious work. And the fact is, as you pointed out, regardless of in the future, right now it doesn't work, and those features are absolutely necessary for writing something like a Linux Kernel module.
> I would like to see an example of a legitimate Linux Kernel module written in Rust that is in some form of use.
Seriously this.
I write an awful lot of kernel code. There's absolutely no way I'd try to convince folks to accept Rust code into the OS repository, where it will have to:
- Be maintained for decades
- Be ported to a litany of platforms, several of which may only have decent toolchains from the GCC department.
- Vend stable ABI
- Not require keeping a litany of compiler versions around just to keep older code building.
> - Not require keeping a litany of compiler versions around just to keep older code building.
Do you have a single example of this? We keep very close tabs on breakage in the wild even, and especially, for changes we were allowed to make (unlike say GCC, which happily breaks code if the language standard says the code never should have compiled in the first place). There are times when we've refused to make changes that we were allowed to make (because they were changes to unspecified behavior, which C/C++ has more of than Rust, and which GCC/Clang changes all the time) out of concern for breaking existing code.
As far as I'm concerned, honestly, this is just FUD.
Your own release notes contain descriptions of breaking language changes.
That may change as the language matures -- great. I keep an eye on Rust so that I can eventually try actually using it for kernel-level development work.
> Which means Swift, .NET Native, Java/C++, C++17, on the OSes from Apple, Google and Microsoft.
You've seen where their OS code comes from, and what it's written in, right?
Just to be very clear, I'm very well-versed in life outside C and imperative programming. This isn't "UNIX culture". This is "systems programming" culture, and it's simply pragmatic.
Mac OS X and their predecessor might have an UNIX heritage, but C was always left for the very lowest layer. Already on NeXT the device drivers were written in Objective-C, being replaced by C++ on Mac OS X.
Anyone paying attention to their Swift talks during the last two WWDCs knows where the boat is steering. Cris is quite clear in stating Swift should be usable in all scenarios where C is being used and Sierra already got some adoption in userland components like the dock and launch deamons, now rewritten in Swift.
Microsoft has declared C89 as good enough with the future being C++ and .NET Native.
The C runtime library was rewritten in C++ with extern C for the public symbols.
The C99 compatibility and upcoming C11 are only done to the extent required by ANSI C++. For anything else there is clang.
As of Windows 8, the device driver framework has been changed to C++ and there was a talk from Herb Sutter where he mentioned the plan was to migrate the kernel to compile with a C++ compiler.
The idea of Core C++ Guidelines actually originated at Microsoft, before Bjarne and CERN guys got involved.
Google doesn't allow native code on ChromeOS and on Android they make pretty clear that the NDK is just to make game developers happy and nothing else.
They are all aware that C isn't going away tomorrow, but are driving efforts to make it as relevant in the future on their platforms as Assembly is today.
When I started working in IT, the only OS written in C was UNIX.
The language is not a sacred cow and the only thing preventing replacing it is the ubiquity of UNIX like OSes.
OS not bound to the UNIX culture and POSIX compatibility are free to chose other language as their systems language.
Not doing so, is usually a decision to cater to the status quo and ubiquity of existing developers and library (with their endless CVE entries).
C++ isn't substantially different from C when it comes to systems programming concerns regarding stability.
The biggest issue is that it's a bit harder to maintain ABI compatibility. You have to carve out reserved vtable space, avoid exposing STL in your interface, etc, but it's doable.
As for Swift, it has heavy userspace dependencies that make it non-viable for kernel work. Rust does much better there.
This makes combining Rust into already existing C code a hassle, as it is generally worse then even just trying to use C and C++ together which is already fairly annoying and error-prone. This is compounded by Rust's safety goals which generally require a different design to a problem then the "C-style" approach would be - IE. You're probably not going to get good Rust code by simply replacing a .c file with a Rust file, because the entire time you're going to have to make use of the unsafe C functions that your program includes.
Perhaps the bottom line is that (for good reason) Rust is much more then just an improved C. And because of that, combining C and Rust is always going to feel like combining two separate systems together with a compatibility layer in-between - because that's really what it is - and that's just not very attractive.
I know little about Zig (I'm reading about it for the first time now) and don't at all predict it to replace C. That said, I would love a language which has a focus on maintaining very good compatibility with C while fixing the various pieces of its design that are fixable within the bounds of the language. For example, I noticed it mentioned in the README that in Zig that pointers are nonnull by default, and can be made nullable by adding the 'maybe' attribute. This is a feature I would love in C, but really isn't there (gcc has a nonnull attribute, but it is really essentially useless and nothing like you'd want). Essentially, I would like a language where it feels like writing code for the same system, but in a different (better) syntax - where I can drop it into a project and it works together with the system like any other .c file (Though with a different compiler, obviously), but the code inside is much better then what you can do in standard C. I think that such a language could retain the reasons why people (like me) still like C, while fixing a lot of the uglier sides to the language that everybody is aware of but aren't going to get fixed any time soon. And perhaps most importantly, I think such a language is definitely possible (Though it may not be able to employ all the features you may want) - but I don't have the time nor probably the skills to really do it well besides list off the things it should fix and shouldn't fix.
As for starting a project though, if you ignore difficulty to learn then I would agree with you: There's probably little reason not to just go for Rust if you're already willing to learn a new language. For most projects the issues I outlined above don't really matter assuming the entire project is written in Rust - besides the want for a language closer to C in design. That said, I readily concede that I have no idea if Rust's borrow-checking semantics could ever work without the extra features they added that take the design away from being 'C-like' - and considering that's one of the definitive features of Rust, is easily worth losing the 'C-like' detail if it is necessary.
But when considering a project like the Linux Kernel, the GNU coreutils, git, GTK, and other various large projects where conversion to Rust is probably impossible without a complete rewrite, being able to use a 'better' C while still retaining the aspects that people like would probably be a nice step in the right direction. The chances of it happening are nil, but it would still be a step in the right direction.