C is entirely as complicated as Rust, if your goal is to write correct software that doesn't crash all the time. It's only a syntactically simple language. Actually making anything interesting with it is _not_ simple.
Quite right. I have 35 years of C under my belt. I can write it in my sleep.
But even so, I can't for the life of me write C code that's as safe as Rust. There are just too many ways to make subtle little mistakes here and there, incrementing a typed pointer by a sizeof by mistake thinking it's a uintptr_t, losing track of ownership and getting a use-after-free, messing up atomic access, mutex deadlocks oh my...
And that's with ALL warnings enabled in CLANG. It's even worse with the default warnings.
It depends on the project. Most of the projects I write in C are very simple, and getting them to work reliably is really not a problem at all.
If you are writing more complicated or "interesting" programs, then I agree, C doesn't give you a good set of tools. But if all you are writing is small libraries or utility programs, C is just fine. In these cases, Rust feels like pulling out a sniper rifle to shoot a target a meter in front of your face (i.e., overkill).
If you are writing complex, large, or very mission-critical programs, then Rust is great to have as a tool as well. But we don't have to take such a black and white view to think that Rust is always the best tool for the job. Or C or Zig or whatever languages for that matter.
I also write C all the time, and it does not crash. There are certainly memory safety concerns with C, but there are also certainly many programmers that can write C code that does not crash all the time.
Bias is a good keyword with respect to CVEs. As long as there is not much Rust code which is relevant to my daily life I think this is not comparable. And the few Rust packages which now ended up on my system, see no regular security support because they pose a maintenance burden, so actually make me less safe: https://www.debian.org/releases/trixie/release-notes/issues....
But the original claim was that "C code crashes all the time" which is blatantly wrong.
To be precise, it was "C is entirely as complicated as Rust, if your goal is to write correct software that doesn't crash all the time." and I think we wouldn't have this discussion if it were uncontroversial. As somebody writing C code and managing a team that writes C code, there is not a particular high effort needed to make C software not crash. It may be too easy to write messy C code that crashes all the time, but this is not at all the same thing.
> writing C code that does not crash all the time is significantly harder/expensive
This is just plain not true. The fact of the matter is that if you stick to good practices, writing C code that does not crash under regular usage is not at all difficult. The problem is that writing C code that is guaranteed not to contain memory issues is extremely hard. But writing programs that don't crash "all the time" is not at all difficult.
This is why I can understand the push to use memory-safe languages to avoid memory safety issues in critical or widely-relied-upon programs. But the main reason you are using it in these cases is to avoid memory safety bugs, not to help you write programs that don't "crash all the time". And even then, the main incentive is that memory safety bugs can be quite severe bugs in areas like networking code, where they might become a significant security risk. The crashes aren't even the main concern here.
It is also not "extremely difficult" to avoid memory-safety issues in most code, although I agree that Rust has a clear advantage. Essentially you need to have a clear policy on memory ownership and avoid pointer arithmetic by using safe buffer and string abstractions. That this often not done in practice is the issue, but I think the narrative that it is impossible in C or "extremely difficult" is more harmful than good as it shifts the blame to the language and rejects responsibility for sloppy or lazy code.
Easier said than done. In the real-world, in real projects, sustaining zero memory-safety bugs in large C codebases is exceedingly rare. Microsoft and Chrome have each reported that ~70% of their serious security bugs are memory‑safety issues, and curl attributes about 40% of its vulnerabilities to using C. Memory safety problems are really hard to avoid in practice in large C codebases. You can't just blame developers.
And, like what I was referring to, it is even more difficult to prove the absence of memory safety issues entirely. We have only managed to actually prove it for a handful of non-trivial programs, like the seL4 microkernel.
Now, this is all an entirely different question to whether the extra work required to write your program in Rust to avoid memory safety problems is actually worthwhile. If you are writing a program with no networking code, no privilege escalation, and no confidential information, the answer is probably that it doesn't matter. If a game has a buffer overflow in some weird edge case, it doesn't really matter. If your data processing code has a problem, you can probably spot that and it probably won't lead to any security concerns. If your file system scanner runs into a memory safety issue, it probably won't negatively impact you that much.
But if your crypto library has memory safety problems, you are in for a bad time.
The real-world is complex with many trade-offs. The real question if in a 1:1 comparable situation and using similar efforts, you could achieve good memory safety in C. I believe this to be the case. I think the statistics we have seen are highly biased and partially misleading and the actual differences in the number of CVEs have many reasons (including legacy code, priorities, usage scnarious, culture etc). I mean, why even mention Curl, a project with portability requirements so extreme that it sticks to a long obsolete C version (30 years!). Proving the absence of memory safety issues entirely may indeed be harder, but ensuring a reasonable safety level is quite possible in my opinion. One should also point out that only safe Rust has guaranteed absence of memory safety issues, but it is Rust - and not safe Rust - that is competing with C.
When a project grows in complexity, age, number of contributors, poor review culture, complex requirements, or any of 101 different things that can make a project hard, then it is hard to avoid memory safety bugs. This covers like 99% of non-trivial projects.
If your software projects are very simple, then it is easy to write simple C programs that do the job well. This is what I said before. If your project is simple, boring, and straight-forward, using C is just fine. It is complex projects where the use of C can become a problem (and complexity can sneak up on you from an unbelievable number of sources).
I would not say we agree with respect to your last point. I think also very complex C programs can be made memory safe, when this is a design goal from the beginning. That complexity can make this harder is true also for Rust, where people then often use unsafe, or, alternatively, re-engineer large parts of their project to get the structure right. C gives you the same choice at this point, but it may be more common to pick the unsafe path because memory safety is not valued high enough to justify the refactoring cost, so people often accept safety issues instead of fixing the design issues in the program.
I don't think a 100-line function signature is representative, but I will point out that the alternative is at least 100 lines of runtime checks instead. In both cases, what a nightmare.
To me that's more an indictment of Diesel than of Rust. I've been using sea-orm for a project I'm working on, and my (generic) pagination function is a hell of a lot simpler and readable than that one.
Typing code from hell for sure, but how would you write an API with the same guarantees in C? Some kind of method specific struct that composes all other kinds of structs/unions to satisfy these requirements?
This is an extremely generic interface to some meta magic DSL. It's complex but not really that complicated and yeah, it's going to be a bit long. But that's going to happen in every language where you rely on types for early validation.
Yuck. I thought some of the signatures you end up with when building “Modern C++” in the Andrei Alexandrescu style were hairy, but this looks sick. Not in a good way.
Probably does something cool for all that crazy though?
Every requirement on the types is commented on why it's necessary.
This is a generic method in the middle of some database DSL code that does a bunch of SQL operations on a type safe manner. Code like this takes "SELECT ?+* FROM ?+* WHERE ? ORDER BY ?+* LIMIT ? OFFSET ?", specifically the limit and offset part, and returns a type that will always map to the database column. If the query is selecting a count of how many Foo each Baz references, this will map to a paginated Foo to Baz count type.
The alternative is to manually write this stuff out in SQL, then manually cast the right types into the basic primitives, which is what a language like Zig probably does.
You'll find similar (though sometimes less type-safe) complex code in just about any ORM/DSL, whether it's written in Java or PHP.
I don't think you can accomplish this in C without some kind of recursive macro parser generating either structs or maybe function pointers on the fly. It'd be hell to make that stuff not leak or double free memory, though.