Rust is not a good C replacement (2019)
drewdevault.com
drewdevault.com
That may be true, but Go has a garbage-collector, and Rust does not. Garbage-collection strikes me as being against the principles of both C and C++, but especially C.
Drew makes some solid points about the merits of C, even despite its considerable downsides, but I have to agree with others here that rewriting in Rust is rarely what people are advocating. Writing new systems in Rust, is usually the point.
Mandatory link to the Spolsky article, Things You Should Never Do, on why you probably shouldn't throw out working code. [0]
> Rust will eventually fail to the “jack of all trades, master of none” problem that C++ has.
Ok, but C++ has been incredibly successful, and it outcompetes C in many domains, just about everything but kernel programming and embedded programming. C++ isn't completely out of the running even there. Versatility is a feature.
Also, Drew (the SourceHut guy, if anyone missed it) has a good sense for minimalist web design. There's no fat on that page. A refreshing change.
[0] https://www.joelonsoftware.com/2000/04/06/things-you-should-...
Linux crashes, Windows bluescreens, Emacs crashes, GCC crashes. Do we have any C that works? I hear good things about seL4 but that was mostly using C as a compilation target from Haskell.
Go and Rust put hard limits on what will compile and especially rust run a whole bunch of checks to enforce book keeping.
In C if you are doing things right you run those checks as a separate process. And you only do it once you are done with a dev cycle, not every time you compile the program.
Firstly, C is where it is because it's well established and has solid foundations and it has remained pretty much unchanged for decades. Rust on the other hand is changing rapidly and fast, which is something you wouldn't want for a language that powers your kernel
Secondly, the Rust community is painfully small.
Thirdly, Rust lives in the LLVM world and I don't see it coming into the GCC world anytime soon. Which is a huge problem, as far as replacing C goes(namely licencing might be problematic in some cases).
Lastly Rust, in terms of it's nature, is more closely related to C++ rather than C. If Rust was to replace something, it has a much better chance against C++ than C.
I truly want to see the day I open up linkedin, go to the jobs section, type in rust and see 6000 new job offers. But at this point in doubt this day will ever come.
You may be surprised... we'll see, and it very much depends on what you mean by "soon," but there have been some interesting rumblings here.
modern c++ is very different nowadays, and it keeps evolving fast, to some extent many modern languages are having the same new features, c++ is becoming better and better for each new standard release, and g++/clang++ also keep up with new sanitizer, static analysis, excellent warning optional flags etc, in fact it is now very hard to make those stupid mistakes these days with modern c++ in a good IDE(even vim with the right configuration).
I will keep my eye on rust, I use a few rust utilities myself, but for the heavy duty of my carerr, c/c++ remains to the the top choice.
Google has, supposedly, some of the best engineers¹ in the world, and the best tooling in the world, and they keep pouring tons of resources on the best engineers and the best tools.
Yet, the same memory safety errors (which Rust, or any hypothetical memory-safe language, prevent entirely) keep popping again, and again. Have you heard of CVE-2020-0022? It's amazing.
In this perspective, pouring resources and effort on those languages is like trying to open a door by banging the head harder. It's fine if you like it, just don't pretend it's the optimal way to open doors.
¹=edited, to make it clearer what I wanted to convey.
I would say, it's SpaceX. (I wonder what they code in.)
Depends on where you look. In some online spaces a lot of Rust advocates do.
When it comes to building user mode applications like desktop programs with QT or GTK or what have you or command line programs such as curl and wget, I think these crowds have a valid point. Most of those applications are written in an object oriented language though.
There's also a very small group that's very militant about everything, including kernels and microcontroller programs, being rewritten into Rust. I don't pay too much attention to that crowd; C has its place and it's not the ultimate solution to programming languages.
Rust won't replace C in the kernel, but I do think we will see tooling for our network clients, file indexing and perhaps even version control slowly switching languages (if they still rely on C, that is). These will all be new tools though, because rewriting an old application into Rust and maintaining compatibility and performance is a big challenge.
Do you mean the Linux kernel, XNU, Free/Net/OpenBSD, or other existing kernel's? There are plenty of examples of folks successfully building kernels in Rust. The likely hood of any of the major kernels adopting it (beyond drivers) is probably low. I expect the only reason that would happen is if any of the folks behind those kernels decide that they actively want to migrate the entire thing away from C in the long-run.
Ground up new kernels in Rust are very feasible, though. Will any of those attract the community and contribution that we see in Linux? That's unlikely, but it's not impossible, as your comment implies.
The only option for kernels that are mostly Rust I can see is a ground-up kernel, but those rarely succeed if ever. There's been small kernels in C# for a while now, for example, but those are usually no more than a fun experiment.
Fuchsia is the closest thing to a completely new kernel that I can think of and despite its age and the amount of people working on it, it's not used outside of lab environments. Perhaps in ten or twenty years a new kernel will replace Linux, and that might very well be written in Rust.
Maybe I want to write a game. Gfx-rs is deprecated. Its replacement? It’s in a half ready beta state, and most of its developers are now focusing on a compatibility wrapper for MacOS, so progress is stalled.
The ecosystem just isn’t there. System libraries have an interface that is extremely suited for C. Interfaces have to be uniquely adapted for rust or be marked completely unsafe. Dynamic linking is convoluted, so Rust encourages statically linking everything. It’s not easy, and the situation needs to improve or Rust will be relegated to just another text processing language. C library symbols need to be accessible as a first-class part of the language.
Perhaps Rust will not be able to interface with Win32 because of its reliance on pointers, but there's the possibility of leveraging alternative programming interfaces such as the abstractions in the .NET runtime.
I believe that if Python and Java can have cross-platform GUI libraries, so can Rust. I don't know what the result would look like but the tooling has already massively improved since I first looked into the language. I'm too annoyed at GTK for their obsession with client-side decorations messing up the native look on Windows and other platforms to use it for my own projects, but the Qt integration didn't look all that bad in my opinion. Rust-qt is clunky because the Qt API requires unsafe code, but with some work the unsafe parts can be abstracted away from the main program logic. It's suboptimal but it does allow you to do data and network traffic parsing and handling in a safe manner, which would already be a huge win for application security in my opinion.
Almost 20 million all time downloads: https://crates.io/crates/winapi
And the other bindings I was talking about https://github.com/microsoft/winrt-rs
I wouldn't be surprised if there are multiple large communities that preach about nodejs replacing c. However I doubt anyone from the core rust developers lives with such illusions.
librsvg largely did. Brian Cantrill was very bullish, and apparently still is given… well the name of his company is a reference for starters, and if you look a Oxide's public repos those with C as the main language are all forks.
> Lastly Rust, in terms of it's nature, is more closely related to C++ rather than C. If Rust was to replace something, it has a much better chance against C++ than C.
That's very true, but at the same time C++ is way more complicated than Rust, with features interacting in subtle pitfalls and a lot more implicit (and often undesirable) behaviours e.g. the Chromium Cursor / GDI leak where a missing (therefore implicitly-declared) copy-assignment operator led to a leak of invisible kernel resources.
Not all of the people who'd rather do C than C++ do so because they don't want anything better than C. Again I'll cite Cantrill who very famously and publicly hated C++.
That said, Oxide is definitely doing The Right Thing(tm) selecting Rust, since they explicitly NEED boot and system board firmware that A. Is fast native code and B. Is as close to semi-sorta-provably-secure as one can get. Rust fits that specific bill better than anything short of C/ASM and NASA-level code validation processes.
To the matter at hand: yes, I have fallen in love with Rust.[4] Yes, I think it is a good fit for system software.[5] And yes, this has served as a bonding force at Oxide -- but as much (if not more!) for the values that it reflects than for it itself.[6]
[1] http://dtrace.org/blogs/bmc/2010/08/11/the-node-js-demograph...
[2] http://dtrace.org/blogs/bmc/2012/05/05/debugging-node-js-mem...
[3] https://vimeo.com/230142234
[4] http://dtrace.org/blogs/bmc/2018/09/18/falling-in-love-with-...
I'm rooting for Zig and Odin as the better evolution for C. So is Carmack, at least for Zig [0] it seems.
[0] https://twitter.com/ID_AA_Carmack/status/1300579679951294464
In the same thread on Rust when asked, "What’s a better modern choice?", the response was: "Rust would be the obvious things [sic], and I don't have any reason to doubt it would be good, but I haven't implemented even a medium sized application in it."
https://twitter.com/ID_AA_Carmack/status/1299574198365495297...
C is so much of a lingua franca -- especially at the ABI boundary -- that any language wishing to "replace" it must properly absorb C. Zig is the language in town succeeding in that effort.
The specific issues I personally have run into with Rust and the C ABI are varargs (possible just not simple) and then dealing with function interfaces defined through CPP macros. Are there others you've seen?
"Not only can Zig compile C code, but there is a very good reason to use Zig as a C compiler: Zig ships with libc"
This is what I mean by properly absorbing C. Below that section, you'll also read:
"One of the primary use cases for Zig is exporting a library with the C ABI for other programming languages to call into."
It is unclear that Rust has shown that level of public commitment.
And hey, one could argue that this stuff doesn't matter: Go famously tried to change its calling conventions. Their functions like to return at least three words, but C-based ABIs reserve at most two registers for function results, forcing Go to have their function return values on the stack.
Rust has pretty great support for the C ABI based on my usage. Using both the bindgen and the cbindgen tools to import/export C ABI, is really simple. The Rust language is committed to always supporting the C ABI, where functions and types are marked as such, for example: 'extern "C" fn', and for types '#[repr(C)]'
Thank you for the links to Zig.
It's pretty fun because Rust was a huge inspiration for Zig, and so for it to turn around and then offer improvements back to Rust is a beautiful cycle :)
I have all the control and performance I wanted from C. Despite Rust being a "bigger" language, it's simpler to use, because it takes care of things for me, instead of relying on me to handle every tedious detail.
It is more portable than C was for me. I don't need to support exotic DSP platforms, but I do need to support Windows. Now I don't have to spoon-feed MSVC, and I don't need to answer endless support tickets about the build system.
Afaik that's intentionally unspecified/implementation-specific so that the language can evolve. The C ABI is the de-facto interoperability layer.
> the memory model
According to the rustonomicon they're leaning entirely on the C++20 memory model at the moment.
This is not really true; we already have one alternative implementation that's good enough to compile the compiler, though it is not complete yet. Also, the gcc folks are interested in getting Rust in, and a formal specification is not a roadblock for them.
> Even the bit about Cargo stems from this: without a specification, Cargo's implementation is the de facto explanation for to properly link external, compiled libraries against rust binaries.
This is not true; this interface to the compiler is stable.
That's really interesting! Can you link it?
Most programmers don't write ISO C; they write GCC/Clang/MSVC C. This is especially true in the systems programming domain. And these variants are frequently poorly-specified, if indeed they are at all. Start using __attribute__ (or __declspec), inline assembly, computed goto, etc., and you find that your program isn't well-specified. Implementation-defined behavior is also supposed to have its behavior documented by the compiler, but adherence to this rule is spotty (clang is the most notorious violator here). And the after-effects of these changes aren't localized: the semantics of __builtin_nontemporal in LLVM imply a change in the specification of atomic fences [1], not that it's documented anywhere in LLVM.
The lack of a specification tends to not be a problem in practice because (and again, especially so for systems programmers) C tends to be viewed as a "portable assembly," and these extensions are mostly gateways for picking specific, more specialized instructions--which are less amenable to optimization. But the main value of the specification is to nail down what can and cannot be optimized, and if you're a person like me who loves pushing the margins of the specification, you'll really notice how desperately needed better specification of these extensions are, even in the C domain.
[1] If you're wondering, LLVM treats the semantics of fences as adhering only to "normal" memory operations. MOVNT, the nontemporal operator in x86, is equivalent to a relaxed store, so you need to do a release fence (SFENCE) to guarantee visibility. However, x86 is TSO, so all regular stores effectively have a SFENCE following them, so release fences compile to nothing instead of SFENCE.
Rust intentionally doesn't have stable ABI. Having a specification wouldn't change that. C++ does have a stable ABI, and pays a cost due to that - for instance `unordered_map` is 3 times slower than it needs to be (the implementation cannot be improved specifically due to having a stable ABI).
Use C ABI if you need components compiled using different rustc version to interoperate.
Linux kernel maintainers doesn't want to use Cargo in the kernel ("Agreed, I wrote one ~half a year ago that compiled TUs through rustc mimicking what we do with cc to some degree (i.e. no cargo etc.), and it was quite pleasant." [1]), and there hasn't really been much opposition to that by the Rust team.
> However, nearly all programs needn’t be parallel. A program which uses poll effectively is going to be simpler, reasonably performant, and have orders of magnitude fewer bugs. “Fearless concurrency” allows you to fearlessly employ bad software design 9 times out of 10.
Sure, if you don't care about performance, which may very well be the case in many situations. However, the reality is that modern CPUs get their performance from multi-core workloads, requiring you to do things in parallel. Go does have some nice bits (e.g. no fragmentation between Tokio and smol, no need to separate code between async and non-async versions), but you don't explain what those are in depth.
> I especially refuse to “rewrite it in Rust”
Sure—that's mostly just a meme[2], which many people in the Rust world acknowledge is a bad thing[3].
[1]: https://lore.kernel.org/lkml/CAKwvOdmuYc8rW_H4aQG4DsJzho=F+d... [2]: https://www.reddit.com/r/rustjerk/comments/av5pog/higherres_... [3]: https://www.reddit.com/r/rust/comments/ifkrvg/i_printed_rewr...
I've seen "rewrite it in Rust" mentioned plenty, even on HN; in fact, that meme colored much of my initial impression of Rust before I learned anything about it directly. I think the maintainers and the Rust world in general need to address that meme head on if they don't want it to be part of how Rust is known.
Otherwise, as it stands currently, Rust is the language that's trying to replace C and rewrite everything that is C in Rust.
Or something like that. Then there's something out there and clearly visible that immediately shuts down the zealotry because the maintainers have said exactly where Rust stands, and what they think it's great at, and what it doesn't do. Which seems like the problem: that the zealots are giving the impression that Rust is better than C for everything.
A year ago: https://news.ycombinator.com/item?id=19482669
Rust can be a good C alternative, which is all I've ever seen Rust people promise it to be.
C is far too widely used/supported to be replaced. A small minority of libraries will be rewritten in Rust, but mostly we'll see new projects that have their own namespaces.
Now this is more of a problem:
- C: 0.73 new features per year
- Go: 2 new features per year
- C++: 11.3 new features per year
- Rust: 15 new features per year
Rust is now stable enough that you can recompile old programs from a few years ago, but more and more features are being added and the ideomatic usage keeps changing.
The borrow checker was brilliant. The rest of the language, not so much.
A point that doesn't get discussed enough is threading models. Go has "green threads" - no need for a distinction between threading and "async". Python and Rust started with threads, and are having "async" bashed into the language as an afterthought. "Green threads" have been around for years, but Go seems to be the first language where they really took off. This may reflect the use case. Go is optimized for big web servers talking to huge numbers of clients, which is what Google does all day.
> Python and Rust started with threads, and are having "async" bashed into the language as an afterthought.
Rust started with green threads, but they were removed pre-1.0 for reasons that I'm sure steveklabnik can jump in and explain.
Here is a very lengthy explanation of the reasons: https://www.infoq.com/presentations/rust-2019/
That really depends on the kind of speed you need. I have, in my brief time on this planet, accomplished each of the following at least once:
Sped up a concurrent implementation by making it parallel.
Sped up a concurrent implementation by making it sequential.
Sped up a parallel implementation by making it concurrent.
Sped up a parallel implementation by making it sequential.
Sped up a sequential implementation by making it concurrent.
Sped up a sequential implementation by making it parallel.
The 2nd and 4th ones are often the easiest to accomplish. Amdahl's law is a great rule of thumb, but it subtly implies an idealized world where both the transition between sequential and parallelizable sections of the process, and the communication among parallel sections, happen at zero cost. The real world is messy, though, so, in practice, they can get pretty expensive. Parallelism can slow things down by introducing extra copying, memory access, memory fences, conditional branches, etc. You've got to dig yourself out of that hole before you can realize any gains. To the extent that the term "order of magnitude" often comes into play when describing the sizes of delays these things can cause, it can be a pretty deep hole.
Java used to be based around green threads back in the early day. Lua's concurrency is based around coroutines. OCaml's OS threads are hampered by a GIL so concurrency is generally done via lwt or Async. And of course "green threads" are the very base of erlang / elixir. And Haskell has… Sparks, I think? You could argue that they're not as popular as Go but then… ok?
> This may reflect the use case.
It definitely does. Rust used to use green threads and everything way back in the olden days. That was stripped out, because it's inimical to being the low-level language its community wanted it to be.
If you want to use green threads, you must have some sort of runtime to manage them (scheduling and preempting, maintaining the task queues, …), and probably a GC because this makes the lifecycle of your values way harder to predict and square. But the environment where the Rust community of the time wanted to use Rust did not allow for such overhead. So out they went, to be added later as an opt-in.
People who don't know Rust don't understand how bare-bones 1.0 was, and that Rust is still filling in gaps in basic functionality. Rust is still catching up with features Go 1.0 had, but people praise Go for being small, while freaking out about Rust getting bloated. This doesn't make sense!
Most of the changes can be summed up "previously code that you'd expect to work didn't work for weird complex reasons, but now we've fixed it and it compiles". Things like `clone()` didn't work on arrays larger than 32 elements. Now it works, as it should have since 1.0. But by Drew's header count that's some frivolous added complexity.
> Rust started with threads, and are having "async" bashed into the language as an afterthought.
Afterthought? That feature was over 3 years in the making. It was probably the most scrutinized and bikeshedded feature Rust ever had. Rust team has explored various approaches, with prototypes implemented in the compiler. People have built an entire ecosystem on Futures 0.1 to work out all the kinks of the design in large production-ready projects.
Proto-Rust had green threads and removed them for a good reason. The trade-offs are well understood, and it's not a coincidence that Go doesn't have zero-cost interoperability with C, and Rust has (including cross-language inlining).
That's more of a money issue. Google has the resources to rewrite the lower layers in or for Go. Mozilla doesn't. Mozilla was trying to retrofit a single existing program, too.
Nowhere in https://github.com/rust-lang/rfcs/pull/230 is Mozilla mentioned, nor expenses.
But a "C replacement" doesn't mean "something similar to C", it means "something that solves the same problems as C". Rust doesn't excel on all the problems that C excels at, but it does for a lot of them. So it is a partial replacement, and isn't at all a total replacement.
Are there other areas where you believe C excels vs. Rust?
Is C's degree of portability important for a C replacement? Depends on the project we're talking about and the platforms it needs to support.
We can go down the line w/ these arguments (some of which like "concurrency is a bad thing" and "I don't care about safety" aren't even serious). I'm also surprised that in 2020 (or 2019 as this was written) we have engineers making a serious argument that specifications are inviolable laws. They do in fact help, but there are more considerations to make about a project's commitments to reasonable, expected behavior than just "there's a spec" (and in fact C has more guardrails supporting those commitments than a spec).
I agree that there's a rust contingent that is over-eager to rewrite and may be oblivious to the concerns of a project or broader technical implications involved in pulling rust into a project.
That being said, this reads more like a "get off my lawn", oblivious tirade about the "cult of the new" than a serious attempt to address these instances.
This is at least the third maybe forth time the article is in the top charts.
I don't feel even like posting my opinion anymore.
The weird thing about Go relative to all this is the fact that interoperates so poorly with C.
Go code doesn't result in the simplest solution possible.
"In Rust We Trust."