The problem is that C have practically no checks, so any safety checks put into the competing language will have a runtime cost, which often is unacceptable. This leads to a strategy of only having checks in “safe” mode. Where the “fast” mode is just as “unsafe” as C.”
So, apparently the author hasn’t used Rust. Or at least hasn’t noticed the various benchmarks showing it to be capable of getting close to C performance, or in some cases outpacing. Also that because of Rust’s safety, it’s much easier to write working parallelised code v.s. C and so you can get a lot of improvements that way.
I’ve written a lot of C over the years (4 years of embedded software PhD), and now never want to go back now I’ve seen what can be done with Rust.
The author notes that he does not consider Rust a C alternative, but rather a C++ alternative:
Like several others I am writing an alternative to the C language (if you read this blog before then this shouldn’t be news!). My language (C3) is fairly recent, there are others: Zig, Odin, Jai and older languages like eC. Looking at C++ alternatives there are languages like D, Rust, Nim, Crystal, Beef, Carbon and others.
Now, you could argue that it’s possible to create a C-like language with a borrow checker. I wonder what that would look like.
C++ is a C alternative. The author dismisses rust without any explanation or justification (I suspect it’s for aesthetic reasons, like “the language is big”). For a lot of targets (non embedded), rust is in fact a valid C alternative, and so is C++.
Adding a borrow checker to C requires adding generics to C, at which point it would be more C++-like than C-like. The borrow checker operates on types and functions parameterized by lifetimes, so generics is not optional, even if you do not add type generics.
Also, not adding type generics is going to make the safety gained from the borrow checker a lot less useful, because now instead of writing the unsafe code for a Vec/HashMap/Lock/… once (in a library) and using it a gazillion times, you write it once per type.
Yes it is. It is why Cyclone added polymorphic functions, polymorphic data structures, and pointer subtyping to C, check Cyclone user manual. Not because they are cool features, but because they are required for memory management.
Even though types and functions are parameterized by lifetimes, they do not affect codegen.
So it should be possible to create a “C with borrowchecker”.
I don’t understand how codegen matters here. Clang and rustc share codegen… If C-like codegen (whatever that is) gives C-with-borrow-checker, C-with-borrow-checker is rustc.
This is still a mysterious position. You seem to think C++-ness of templates comes from monomorphisation code generation strategy, but most would say it comes from its frontend processing. Monomorphisation is backend implementation detail and does not affect user complexity, and as for implementation complexity it is among the simpler one to implement.
the whole point was that generics are not needed for a language to have a borrowchecker.
if you call a generic function twice with different types, two function code is generated.
if you call a generic function twice with different lifetimes, only one function code is generated.
borrowchecker is anmotation + static analysis. the generated code is the same. the same is not true for generics or templates.
If you think this, you should write a paper. Yes, borrow checker is a static analysis. As it currently exists, it is a static analysis formulated to work on generics. As far as I know, no one knows how to do the same without generics.
I disagree. To me Rust is a great C replacement, and Rust is incompatible with C++ both technically and philosophically. I’ve used C for two decades. I’ve never liked C++, but really enjoy Rust. I’ve written a C to Rust transpiler and converted projects from C to Rust.
C programs can be converted to Rust. C and Rust idioms are different, but language features match and you can refactor a C program into a decent Rust program. OTOH C++ programs can’t be adapted to Rust easily, and for large programs it’s daunting. It’s mostly because Rust isn’t really an OOP language (it only has some OOP-like syntax sugar).
I think people superficially see that both Rust and C++ are “big” and have angle brackets, and conclude they must be the same. But Rust is very different from C++. Rust doesn’t have inheritance, doesn’t have constructors, doesn’t have move constructors, doesn’t use exceptions for error handling. Rust’s iterator is a completely different beast than C++ iterators. Rust’s macros are closer to C++ templates than Rust’s generics. Lots of Rust’s language design decisions are at odds with C++’s.
Rust is more like an ML language with a C subset, than C++.
When you say “replacement”, what do you mean, exactly? For example, could C++ or Ada be great C replacements?
I think some of the disagreements about Rust - and the whole trope about Rust being a “big” language - come from different people wanting different things from their “C replacement”. A lot of people - or maybe just a particularly vocal group of people on Internet forums - seem to like C not just because it can be used to write small, fast, native code, but because they enjoy the aesthetic experience of programming in C. For that sort of person, I think Rust is much more like C++ than C.
Rust is very different from C++. Rust doesn’t have inheritance, doesn’t have constructors, doesn’t have move constructors, doesn’t use exceptions for error handling.
Modern C++ (for some value of “modern”) doesn’t typically have inheritance or exceptions either. I’ve had the misfortune to program in C++ for a couple of decades now. When I started, it was all OO design - the kind of thing that people make fun of in Enterprise Java - but these days it’s mostly just functions in namespaces. When I first tried Rust, I thought it was just like C++ only I’d find out when I screwed up at compile-time rather than with some weird bug at run-time. I had no trouble with the borrow checker, as it just enforced the same rules that my team already followed in C++.
I’ve never liked C++ because it’s too complicated. Nobody can remember the whole language and no two teams use the same subset of the language (and that applies to the same team at two different times too, as people leave and join). People who program alone, or only ever work in academia in small teams, might love the technical power it offers, but people who’ve actually worked with it in large teams, or long-lived teams, in industry, tend to have a dimmer view of it. I can see why people who have been scarred by C++ might be put off by Rust’s aesthetic similarity to it.
I’ve never liked C++ because it’s too complicated. Nobody can remember the whole language and no two teams use the same subset of the language (and that applies to the same team at two different times too, as people leave and join).
I think that’s a correct observation, but I think that’s because C++ standard library and the language itself has over 3 decades of heavy, wide industry use across much of the depth and breadth of the software development, generating demands and constraints from every corner of the industry.
I do not think we had ‘solved’ the problem of theoretically plausible definition of the minimal, but sufficient set of language features + standard library features, that will be enough for 30+ years of use across everything.
So all we have right now is C++ as a ‘reference stick’.
If a newbie language compares well to that yardstick, we hale it.
But is that the right yard stick?
I definitely don’t use C for an “aesthetic experience” (I do not enjoy aesthetics of the preprocessor, spiral types, tediousness of malloc or header files). I would consider C++ also a C replacement in the same technical sense as Rust (native code with minimal runtime, C ABI, ±same performance), but to me Rust addresses C’s problems better than C++ does.
Even though C++ is approximately a C superset, and Rust is sort-of a C superset too, Rust and C++ moved away from C in different directions (multi-paradigm mainly-OOP with sugar vs ML with more explicitness and hindsight). Graphical representation:
Rust <------ C ----> C++
which is why I consider Rust closer to C than C++.
Sorry for the obvious bait, but if you don’t like C, why do you use it? :-). If you’re looking for a non OOP language that can replace C, well, there’s a subset of C++ for that, and it’s mostly better: replace malloc with smart pointers, enjoy the RAII, enjoy auto, foreach loops, having data structures available to you, etc.
In my C days I’ve been jealous of monomorphic std::sort and destructors. C++ has its benefits, but they never felt big enough for me to outweigh all the baggage that C++ brings. C++ still has many of C’s annoyances like headers, preprocessor, wonky build systems, dangerous threading, and pervasive UB. RAII and smart pointers fix some unsafety, but temporaries and implicit magic add new avenues for UAF. So it’s a mixed bag, not a clear improvement.
I write libraries, and everyone takes C without asking. But with C++ people have opinions. Some don’t like when C++ is used like “C with classes”. There are conundrums like handling constructor failures given that half of C++ users bans exceptions and the other half dislikes DIY init patterns. I don’t want to keep track of what number the Rule of $x is at now, or what’s the proper way to init a smart pointer in C++$year, and is that still too new or deprecated already.
Zig is definitely more C-like, but it doesn’t have borrow checking. I think Vale is closer. There’s MS Checked-C too.
But I’m afraid that “C-like” and safe are at odds with each other. I don’t mean it as a cheap shot against C, but if you want the compiler to guarantee safety at compilation time, you need to make the language easy to robustly analyze. This in turn requires a more advanced static type system that can express more things, like ownership, slices, and generic collections. This quickly makes the language look “big” and not C-like.
It doesn’t. As I understand, its current plan for temporal memory safety is quarantine. Quarantine is a good idea, but if it was enough, C would be memory safe too.
Android shipped malloc with quarantine, and here is what they say about it:
(Quarantine) is fairly costly in terms of performance and memory footprint, is mostly controlled by runtime options and is disabled by default.
I think the big point here is that the author is talking in hypotheticals that don’t always pan out in practice. Theoretically, a perfectly written c program will execute faster than a rust program because it does not have safety checks.
That being said, in many cases those limited safety checks actually turn out to be a miniscule cost. Also, as you mentioned rust may unlock better threading and other performance gains.
Lastly, i think it is important to note that often software architecture, algorithms, and cache friendliness will matter much much more than rust vs c vs other low level languages.
C tooling may feel indispensable and irreplaceable, but I think it’s an illusion. There’s a lot of it, and it’s complex, but that’s mainly because C has a lot of self-inflicted problems that need the tooling.
There’s a plethora of build systems only because there’s a bunch of compilers that are incompatible with each other for trivial reasons (like the lack of standard flags and paths), and each project’s layout and configuration is a snowflake. There are too many fragile and non-portable of ways of using dependencies, with even more tooling to sort that out, only because C’s compilation model lacks a robust concept of modules/namespaces/dependencies in the first place.
C has some advanced analysis tools, but these tools mostly focus on C-specific problems. They are complex, because C is uniquely difficult to analyze. C tools can do heroic things to find incorrect uses of memcpy, but you don’t need that if your language has a safe idiom for it. Similarly with other kinds of UB, lifetimes, non-nullable types, missed cleanups, etc.
Plus C-killer languages can typically reuse many C tools. There’s common LLVM infrastructure for fuzzers and sanitizers. Debuggers, code coverage, and profilers happen to just work with anything that can mimic C ABI and debug info.
I find the tooling part interesting, because nearly all that tooling these days is built for C++, and C just gets to go along for the ride. I don’t see any reason why another language would be a worse fit in that respect. Well, apart from C (almost) being a babytalk subset of C++.
The reason C as of today is performant is there exists giant optimizing compilers that have reasonable freedom to mess around with the code. Try to compile it with tcc instead and see what you give up. A language can be faster than C by making more guarantees than C does. If there is no risk for pointer aliasing, for example, the compiler can go further. Another way to be faster than C is to expose operations that the compiler would have to infer from the C code, which doesn’t always work, like if you have explicit vector operations. A third way to be faster than C is to have generics with specialization for particular datatypes, where C++ shines. Another way is to make it easy to evaluate stuff at compile time.
And with LLVM you can get half of that giant optimizing compiler for your pet language for free.
I don’t often see another item mentioned that I’d like to offer for consideration.
I think my first attempt to articulate this may have been on the orange site in a discussion about C and security, and much later about X and Xenocara there and on this site.
This has always felt like a squirrely concept to wrap the English language around, and nearly impossible to articulate in a world philosophically-dominated by the ideas that descend from the bazaar-style approach of GNU/Linux in software design. I’m still not sure how to convey the idea to a general audience, but I highly value the internal design coherency and adherence to significant portions of the original K&R version of a unix philosophy that spirals off to touch on allkindsofother ideas (and even unrelated programming languages built on C) when talking about the value of C applications and systems.
It’s not that “C is the only way” by any stretch; rather, I don’t want to see anything except a single programming language (and compiler, and code style, and architectural principles, and standardization of IPC, and so on) used on everything that comprises an essential base layer of my computing. And I want that layer to be simple enough that the people I personally know today could recreate that language, its toolchain, and its entire ecosystem from first principles in a post-computing apocalypse. Being able to survive the eventual abandonment (or worse, destruction) of complex institutions is significantly more important to me than something like avoiding memory leaks or closing security holes.
If Rust (or anything else) replaced C in that capacity, I’d be perfectly happy to abandon the ~40 years of work and knowledge already accrued to that language and its collective of wise gray beards with their younger journeymen, apprentices, and disciples.
I often invoke OpenBSD as my example because it seems absurd to people who don’t use OpenBSD that it might be possible to open a driver (or any other component) of an operating system’s source code - having never done so before - and be able to understand how it works it based on its similarity to source code you inspected years ago in a userland application.
C isn’t the reason for that, but C has a lot to do with that. It would be hard for me to continue my attempted explanation of personally valuing the C language and the other humans who can write it without straying wildly off-topic into a rant about the Bronze Age collapse, so I’ll leave it there.
I take it you mean to ask how many people it would take to exactly recreate a new equivalent to C from first principles after the many decades of its existing knowledge evaporated? I’m confident in saying it would take approximately 1 extremely bright person approximately 1 year of labor, since that’s how long it took Ritchie (after which it took a small group of other exceptional engineers only a brief period to write an entire operating system and its userland of applications based on that language).
I want to be clear that I think Rust (for example) is a wonderful and powerful piece of technology, so I unfairly pick on it here merely to provide an illustrative counterexample:
I know Rust has had sponsorship for at least the past decade, and it looks like the 2022 budget of the Rust Foundation is $625,000. I don’t work on Rust, and I am undoubtedly even further below their level of technical ability and productivity than I would be if compared to Ritchie (which would already be many orders of magnitude), but it doesn’t seem unreasonable to suggest that no individual on earth could recreate the entire Rust toolchain from first principles in their own lifetime. And since we also don’t have a Rust operating system with Rust hardware drivers and a Rust userland after more than 10 years of its funding, it would be impossible to make any assessment on how long it would take - there is no evidence to draw upon.
That I’m so much less capable and clever than the collection of people and institutions working on C alternatives is the essence of my point.
I’m not concerned that potential C alternatives aren’t as sophisticated or powerful as C, but rather the precise opposite.
but it doesn’t seem unreasonable to suggest that no individual on earth could recreate the entire Rust toolchain from first principles in their own lifetime.
https://github.com/thepowersgang/mrustc is a functional Rust compiler, which was implemented, as far as I know, by a single person in a rather short timeframe. Heck, I guess I can say that I myself implemented a significant chunk of Rust compiler twice :-)
Rust is undoubtedly way harder to do than C, but still seems rather within reach of a single dedicated person.
I used the example of Rust as it is the most commonly discussed language around this topic, but I don’t write Rust myself or have any knowledge of its ecosystem, so I may not understand your meaning out of unfamiliarity. The introduction of this project page reads:
This project is a “simple” rust compiler written in C++ that is able to bootstrap a “recent” rustc, but may eventually become a full separate re-implementation. As mrustc’s primary goal is bootstrapping rustc, and as such it tends to assume that the code it’s compiling is valid (and any errors in the generated code are mrustc bugs). Code generation is done by emitting a high-level assembly (currently very ugly C, but LLVM/cretone/GIMPLE/… could work) and getting an external tool (i.e. gcc) to do the heavy-lifting of optimising and machine code generation.
I apologize for my own ignorance - again, I do not work in Rust (but also see no reason it shouldn’t grow into a widely adopted language for a broad spectrum of software) - but this project seems dramatically at odds with an assertion that an individual could have produced this from first principles themselves, and perhaps even serves strongly as counter evidence. Am I misunderstanding?
Perhaps it’s me who is misunderstanding what “producing from first principles” entails: a working simple compiler that can compile “bells and whistles” compiler seems to fit the bill.
If the objection is to cutting corners (assuming the code is valid, using “external assembler”, etc), than that’s I think how the first versions of C worked as well (it had scarce any typechecking). Getting C toolchain to modern gcc/clang state required quite a bit of effort.
If the objection is to being implemented in C++ (so, requiring some existing supporting infra), than, yes, Rust is indeed one step further on the bootstrap chain than C. But C also isn’t exactly a clean-slate thing: it incrementally started from a compiler for a language which didn’t even have structs, it kinda required preceding 30 years of computer evolution. I think the right way to capture is this: the incremental diff from the “previous” step is much larger for Rust, but it still manageable. The non-incremental diff from zero depends on what you consider to be zero: if it is the computing world just before C appeared, than it’s much larger for Rust, if that’s the computing world just after the first computers appeared, than the relevant differences between Rust and C seem to shrink again.
An OS in rust, like redox ? Of course it doesn’t have all the drivers linux has, but it exists. For C++ it’s the same, interestingly: SerenityOS was built from scratch in a few years, with the full stack from the kernel to a graphical userland. These are doable and arguably doable more productively than in C.
I feel the core issue with the article is that it is asking if we can “replace” C. When you look across many languages in programming history, they don’t tend to die, they don’t tend to be replaced. Slowly, they will be used less as other languages are picked for x or y reason, but that is very different than replacing the language as a whole. Replacing C as a whole will probably never happen. Picking up a project her and a project there, that is more possible, but C will likely live on for an extremely long time.
There are counter examples, like objective c to swift, but that is in a very specific scope with a lot of company control.
I find it interesting that these alternatives are being made and I think it’s just good that people are trying things out.
C may still be around when I look like Gandalf but probably more and more in embedded systems than “normal” computer platforms. But I wouldn’t go around dismissing any C alternatives.
Zig looks really nice to me, and I’m also interested in Rust. IMO what will make these languages stay alive is how many different architectures can they support in the end.
My thought was that C can compile on pretty much anything. So if someone wants to replace C, they also have to be able to compile on same things. I don’t see C going away otherwise.
I think the OP is talking about small embedded systems using microcontrollers. For example, there’s SDCC that compiles C for a bunch of 8 and 16-bit microcontrollers. The Wikipedia article links to some others, and there are a ton of proprietary C compilers for those systems.
There’s a huge amount of code written in that space, and I don’t think Rust has any intention of supporting it.
Yes, but it’s still mysterious why Rust and Zig “staying alive” has anything to do with those architectures. Completely replacing C, yes, but not staying alive. From OP’s reply, I think it was just a mistake and in fact completely replacing C was the intended sense.
So, apparently the author hasn’t used Rust. Or at least hasn’t noticed the various benchmarks showing it to be capable of getting close to C performance, or in some cases outpacing. Also that because of Rust’s safety, it’s much easier to write working parallelised code v.s. C and so you can get a lot of improvements that way.
I’ve written a lot of C over the years (4 years of embedded software PhD), and now never want to go back now I’ve seen what can be done with Rust.
The author notes that he does not consider Rust a C alternative, but rather a C++ alternative:
Now, you could argue that it’s possible to create a C-like language with a borrow checker. I wonder what that would look like.
C++ is a C alternative. The author dismisses rust without any explanation or justification (I suspect it’s for aesthetic reasons, like “the language is big”). For a lot of targets (non embedded), rust is in fact a valid C alternative, and so is C++.
For a lot of targets, especially embedded, Rust is an amazing C alternative. Granted, currently it’s primarily ARM Cortex M that has 1st class support but I find your remark funny how from my perspective embedded is probably the best application of Rust and its features. Representing HW peripherals as type-safe state machines that won’t compile if you missuse them? Checked. Concurrency framework providing sane interrupt handling with priorities that is data race and deadlock free without any runtime overhead that won’t compile if you violate its invariants?. Checked. Embedded C is a joke in comparison.
Adding a borrow checker to C requires adding generics to C, at which point it would be more C++-like than C-like. The borrow checker operates on types and functions parameterized by lifetimes, so generics is not optional, even if you do not add type generics.
Also, not adding type generics is going to make the safety gained from the borrow checker a lot less useful, because now instead of writing the unsafe code for a Vec/HashMap/Lock/… once (in a library) and using it a gazillion times, you write it once per type.
Isn’t this more or less what Cyclone is?
Yes it is. It is why Cyclone added polymorphic functions, polymorphic data structures, and pointer subtyping to C, check Cyclone user manual. Not because they are cool features, but because they are required for memory management.
Even though types and functions are parameterized by lifetimes, they do not affect codegen. So it should be possible to create a “C with borrowchecker”.
I don’t understand how codegen matters here. Clang and rustc share codegen… If C-like codegen (whatever that is) gives C-with-borrow-checker, C-with-borrow-checker is rustc.
ops, I guess I should’ve said “lifetimes do not affect monorphisation”
This is still a mysterious position. You seem to think C++-ness of templates comes from monomorphisation code generation strategy, but most would say it comes from its frontend processing. Monomorphisation is backend implementation detail and does not affect user complexity, and as for implementation complexity it is among the simpler one to implement.
the whole point was that generics are not needed for a language to have a borrowchecker.
if you call a generic function twice with different types, two function code is generated. if you call a generic function twice with different lifetimes, only one function code is generated.
borrowchecker is anmotation + static analysis. the generated code is the same. the same is not true for generics or templates.
If you think this, you should write a paper. Yes, borrow checker is a static analysis. As it currently exists, it is a static analysis formulated to work on generics. As far as I know, no one knows how to do the same without generics.
There was some recent discussion on Garnet which is an attempt to make a smaller version of Rust.
This is the correct position on Rust.
I disagree. To me Rust is a great C replacement, and Rust is incompatible with C++ both technically and philosophically. I’ve used C for two decades. I’ve never liked C++, but really enjoy Rust. I’ve written a C to Rust transpiler and converted projects from C to Rust.
C programs can be converted to Rust. C and Rust idioms are different, but language features match and you can refactor a C program into a decent Rust program. OTOH C++ programs can’t be adapted to Rust easily, and for large programs it’s daunting. It’s mostly because Rust isn’t really an OOP language (it only has some OOP-like syntax sugar).
I think people superficially see that both Rust and C++ are “big” and have angle brackets, and conclude they must be the same. But Rust is very different from C++. Rust doesn’t have inheritance, doesn’t have constructors, doesn’t have move constructors, doesn’t use exceptions for error handling. Rust’s iterator is a completely different beast than C++ iterators. Rust’s macros are closer to C++ templates than Rust’s generics. Lots of Rust’s language design decisions are at odds with C++’s.
Rust is more like an ML language with a C subset, than C++.
When you say “replacement”, what do you mean, exactly? For example, could C++ or Ada be great C replacements?
I think some of the disagreements about Rust - and the whole trope about Rust being a “big” language - come from different people wanting different things from their “C replacement”. A lot of people - or maybe just a particularly vocal group of people on Internet forums - seem to like C not just because it can be used to write small, fast, native code, but because they enjoy the aesthetic experience of programming in C. For that sort of person, I think Rust is much more like C++ than C.
Modern C++ (for some value of “modern”) doesn’t typically have inheritance or exceptions either. I’ve had the misfortune to program in C++ for a couple of decades now. When I started, it was all OO design - the kind of thing that people make fun of in Enterprise Java - but these days it’s mostly just functions in namespaces. When I first tried Rust, I thought it was just like C++ only I’d find out when I screwed up at compile-time rather than with some weird bug at run-time. I had no trouble with the borrow checker, as it just enforced the same rules that my team already followed in C++.
I’ve never liked C++ because it’s too complicated. Nobody can remember the whole language and no two teams use the same subset of the language (and that applies to the same team at two different times too, as people leave and join). People who program alone, or only ever work in academia in small teams, might love the technical power it offers, but people who’ve actually worked with it in large teams, or long-lived teams, in industry, tend to have a dimmer view of it. I can see why people who have been scarred by C++ might be put off by Rust’s aesthetic similarity to it.
I think that’s a correct observation, but I think that’s because C++ standard library and the language itself has over 3 decades of heavy, wide industry use across much of the depth and breadth of the software development, generating demands and constraints from every corner of the industry.
I do not think we had ‘solved’ the problem of theoretically plausible definition of the minimal, but sufficient set of language features + standard library features, that will be enough for 30+ years of use across everything.
So all we have right now is C++ as a ‘reference stick’. If a newbie language compares well to that yardstick, we hale it. But is that the right yard stick?
I definitely don’t use C for an “aesthetic experience” (I do not enjoy aesthetics of the preprocessor, spiral types, tediousness of malloc or header files). I would consider C++ also a C replacement in the same technical sense as Rust (native code with minimal runtime, C ABI, ±same performance), but to me Rust addresses C’s problems better than C++ does.
Even though C++ is approximately a C superset, and Rust is sort-of a C superset too, Rust and C++ moved away from C in different directions (multi-paradigm mainly-OOP with sugar vs ML with more explicitness and hindsight). Graphical representation:
which is why I consider Rust closer to C than C++.
Sorry for the obvious bait, but if you don’t like C, why do you use it? :-). If you’re looking for a non OOP language that can replace C, well, there’s a subset of C++ for that, and it’s mostly better: replace malloc with smart pointers, enjoy the RAII, enjoy
auto
, foreach loops, having data structures available to you, etc.In my C days I’ve been jealous of monomorphic
std::sort
and destructors. C++ has its benefits, but they never felt big enough for me to outweigh all the baggage that C++ brings. C++ still has many of C’s annoyances like headers, preprocessor, wonky build systems, dangerous threading, and pervasive UB. RAII and smart pointers fix some unsafety, but temporaries and implicit magic add new avenues for UAF. So it’s a mixed bag, not a clear improvement.I write libraries, and everyone takes C without asking. But with C++ people have opinions. Some don’t like when C++ is used like “C with classes”. There are conundrums like handling constructor failures given that half of C++ users bans exceptions and the other half dislikes DIY init patterns. I don’t want to keep track of what number the
Rule of $x
is at now, or what’s the proper way to init a smart pointer in C++$year
, and is that still too new or deprecated already.Zig fits in that space, no?
Zig is definitely more C-like, but it doesn’t have borrow checking. I think Vale is closer. There’s MS Checked-C too.
But I’m afraid that “C-like” and safe are at odds with each other. I don’t mean it as a cheap shot against C, but if you want the compiler to guarantee safety at compilation time, you need to make the language easy to robustly analyze. This in turn requires a more advanced static type system that can express more things, like ownership, slices, and generic collections. This quickly makes the language look “big” and not C-like.
I don’t think Zig has borrow checking?
It doesn’t. As I understand, its current plan for temporal memory safety is quarantine. Quarantine is a good idea, but if it was enough, C would be memory safe too.
Android shipped malloc with quarantine, and here is what they say about it:
Ada has made the same claims since 1983, and hasn’t taken over the world. Maybe Rust will do better.
I think the big point here is that the author is talking in hypotheticals that don’t always pan out in practice. Theoretically, a perfectly written c program will execute faster than a rust program because it does not have safety checks.
That being said, in many cases those limited safety checks actually turn out to be a miniscule cost. Also, as you mentioned rust may unlock better threading and other performance gains.
Lastly, i think it is important to note that often software architecture, algorithms, and cache friendliness will matter much much more than rust vs c vs other low level languages.
C tooling may feel indispensable and irreplaceable, but I think it’s an illusion. There’s a lot of it, and it’s complex, but that’s mainly because C has a lot of self-inflicted problems that need the tooling.
There’s a plethora of build systems only because there’s a bunch of compilers that are incompatible with each other for trivial reasons (like the lack of standard flags and paths), and each project’s layout and configuration is a snowflake. There are too many fragile and non-portable of ways of using dependencies, with even more tooling to sort that out, only because C’s compilation model lacks a robust concept of modules/namespaces/dependencies in the first place.
C has some advanced analysis tools, but these tools mostly focus on C-specific problems. They are complex, because C is uniquely difficult to analyze. C tools can do heroic things to find incorrect uses of
memcpy
, but you don’t need that if your language has a safe idiom for it. Similarly with other kinds of UB, lifetimes, non-nullable types, missed cleanups, etc.Plus C-killer languages can typically reuse many C tools. There’s common LLVM infrastructure for fuzzers and sanitizers. Debuggers, code coverage, and profilers happen to just work with anything that can mimic C ABI and debug info.
I find the tooling part interesting, because nearly all that tooling these days is built for C++, and C just gets to go along for the ride. I don’t see any reason why another language would be a worse fit in that respect. Well, apart from C (almost) being a babytalk subset of C++.
The reason C as of today is performant is there exists giant optimizing compilers that have reasonable freedom to mess around with the code. Try to compile it with tcc instead and see what you give up. A language can be faster than C by making more guarantees than C does. If there is no risk for pointer aliasing, for example, the compiler can go further. Another way to be faster than C is to expose operations that the compiler would have to infer from the C code, which doesn’t always work, like if you have explicit vector operations. A third way to be faster than C is to have generics with specialization for particular datatypes, where C++ shines. Another way is to make it easy to evaluate stuff at compile time.
And with LLVM you can get half of that giant optimizing compiler for your pet language for free.
Nowadays, most code that needs to be “faster than C” is written in assembly. LuaJIT, video decoders, some video encoders, image decoders, etc.
I don’t often see another item mentioned that I’d like to offer for consideration.
I think my first attempt to articulate this may have been on the orange site in a discussion about C and security, and much later about X and Xenocara there and on this site.
This has always felt like a squirrely concept to wrap the English language around, and nearly impossible to articulate in a world philosophically-dominated by the ideas that descend from the bazaar-style approach of GNU/Linux in software design. I’m still not sure how to convey the idea to a general audience, but I highly value the internal design coherency and adherence to significant portions of the original K&R version of a unix philosophy that spirals off to touch on all kinds of other ideas (and even unrelated programming languages built on C) when talking about the value of C applications and systems.
It’s not that “C is the only way” by any stretch; rather, I don’t want to see anything except a single programming language (and compiler, and code style, and architectural principles, and standardization of IPC, and so on) used on everything that comprises an essential base layer of my computing. And I want that layer to be simple enough that the people I personally know today could recreate that language, its toolchain, and its entire ecosystem from first principles in a post-computing apocalypse. Being able to survive the eventual abandonment (or worse, destruction) of complex institutions is significantly more important to me than something like avoiding memory leaks or closing security holes.
If Rust (or anything else) replaced C in that capacity, I’d be perfectly happy to abandon the ~40 years of work and knowledge already accrued to that language and its collective of wise gray beards with their younger journeymen, apprentices, and disciples.
I often invoke OpenBSD as my example because it seems absurd to people who don’t use OpenBSD that it might be possible to open a driver (or any other component) of an operating system’s source code - having never done so before - and be able to understand how it works it based on its similarity to source code you inspected years ago in a userland application.
C isn’t the reason for that, but C has a lot to do with that. It would be hard for me to continue my attempted explanation of personally valuing the C language and the other humans who can write it without straying wildly off-topic into a rant about the Bronze Age collapse, so I’ll leave it there.
How many people would be able to exactly recreate C, including all obscure edge cases, out of memory?
I take it you mean to ask how many people it would take to exactly recreate a new equivalent to C from first principles after the many decades of its existing knowledge evaporated? I’m confident in saying it would take approximately 1 extremely bright person approximately 1 year of labor, since that’s how long it took Ritchie (after which it took a small group of other exceptional engineers only a brief period to write an entire operating system and its userland of applications based on that language).
I want to be clear that I think Rust (for example) is a wonderful and powerful piece of technology, so I unfairly pick on it here merely to provide an illustrative counterexample:
I know Rust has had sponsorship for at least the past decade, and it looks like the 2022 budget of the Rust Foundation is $625,000. I don’t work on Rust, and I am undoubtedly even further below their level of technical ability and productivity than I would be if compared to Ritchie (which would already be many orders of magnitude), but it doesn’t seem unreasonable to suggest that no individual on earth could recreate the entire Rust toolchain from first principles in their own lifetime. And since we also don’t have a Rust operating system with Rust hardware drivers and a Rust userland after more than 10 years of its funding, it would be impossible to make any assessment on how long it would take - there is no evidence to draw upon.
That I’m so much less capable and clever than the collection of people and institutions working on C alternatives is the essence of my point.
I’m not concerned that potential C alternatives aren’t as sophisticated or powerful as C, but rather the precise opposite.
https://github.com/thepowersgang/mrustc is a functional Rust compiler, which was implemented, as far as I know, by a single person in a rather short timeframe. Heck, I guess I can say that I myself implemented a significant chunk of Rust compiler twice :-)
Rust is undoubtedly way harder to do than C, but still seems rather within reach of a single dedicated person.
I used the example of Rust as it is the most commonly discussed language around this topic, but I don’t write Rust myself or have any knowledge of its ecosystem, so I may not understand your meaning out of unfamiliarity. The introduction of this project page reads:
I apologize for my own ignorance - again, I do not work in Rust (but also see no reason it shouldn’t grow into a widely adopted language for a broad spectrum of software) - but this project seems dramatically at odds with an assertion that an individual could have produced this from first principles themselves, and perhaps even serves strongly as counter evidence. Am I misunderstanding?
Perhaps it’s me who is misunderstanding what “producing from first principles” entails: a working simple compiler that can compile “bells and whistles” compiler seems to fit the bill.
If the objection is to cutting corners (assuming the code is valid, using “external assembler”, etc), than that’s I think how the first versions of C worked as well (it had scarce any typechecking). Getting C toolchain to modern gcc/clang state required quite a bit of effort.
If the objection is to being implemented in C++ (so, requiring some existing supporting infra), than, yes, Rust is indeed one step further on the bootstrap chain than C. But C also isn’t exactly a clean-slate thing: it incrementally started from a compiler for a language which didn’t even have structs, it kinda required preceding 30 years of computer evolution. I think the right way to capture is this: the incremental diff from the “previous” step is much larger for Rust, but it still manageable. The non-incremental diff from zero depends on what you consider to be zero: if it is the computing world just before C appeared, than it’s much larger for Rust, if that’s the computing world just after the first computers appeared, than the relevant differences between Rust and C seem to shrink again.
An OS in rust, like redox ? Of course it doesn’t have all the drivers linux has, but it exists. For C++ it’s the same, interestingly: SerenityOS was built from scratch in a few years, with the full stack from the kernel to a graphical userland. These are doable and arguably doable more productively than in C.
I feel the core issue with the article is that it is asking if we can “replace” C. When you look across many languages in programming history, they don’t tend to die, they don’t tend to be replaced. Slowly, they will be used less as other languages are picked for x or y reason, but that is very different than replacing the language as a whole. Replacing C as a whole will probably never happen. Picking up a project her and a project there, that is more possible, but C will likely live on for an extremely long time.
There are counter examples, like objective c to swift, but that is in a very specific scope with a lot of company control.
I find it interesting that these alternatives are being made and I think it’s just good that people are trying things out. C may still be around when I look like Gandalf but probably more and more in embedded systems than “normal” computer platforms. But I wouldn’t go around dismissing any C alternatives.
Zig looks really nice to me, and I’m also interested in Rust. IMO what will make these languages stay alive is how many different architectures can they support in the end.
Why do you think architecture support is important? Rust supports all Debian architectures, and this seems enough in practice for most purposes.
My thought was that C can compile on pretty much anything. So if someone wants to replace C, they also have to be able to compile on same things. I don’t see C going away otherwise.
Maybe not the most important thing though.
I think the OP is talking about small embedded systems using microcontrollers. For example, there’s SDCC that compiles C for a bunch of 8 and 16-bit microcontrollers. The Wikipedia article links to some others, and there are a ton of proprietary C compilers for those systems.
There’s a huge amount of code written in that space, and I don’t think Rust has any intention of supporting it.
Yes, but it’s still mysterious why Rust and Zig “staying alive” has anything to do with those architectures. Completely replacing C, yes, but not staying alive. From OP’s reply, I think it was just a mistake and in fact completely replacing C was the intended sense.
Ah, yeah, that’s what I meant. Sorry for being unclear.
A fairly interesting article, and there’s also some great discussion in the comments along with links to yet more discussions elsewhere.