A “Better C” Benchmark
zserge.com
zserge.com
I didn't like that C itself was missing, because C is still my language of choice for some tasks and I still use it because I'm happy about it, I don't feel I would need any of its competitors. Maybe if this benchmark included C it would be some argument to give them a try. So I decided to do it for you.
It took me 30 minutes, only change is that I don't check if file is binary, I guess. I liked that I was doing it in C because I know it and I'm comfortable with it. Few checks in man and everything's clear. Only disadvantage of C is that it doesn't have standard API for directories, so my code is only for POSIX. Here's the code: https://github.com/BartekLew/dumbgrep
The other thing are sources you've done. They are almost the same (it'd be nice to see the more feeling of the language, idiomatic code, etc.) and I didn't like reading them at all, not a clean code and all are longer than my C code.
Another thing is that C has very narrow application nowadays. A benchmark should be focussed on them and not on generic algorithms.
I would argue that this is why a "better C" can be useful, it's the kind of error that would be much harder to make in rust.
EDIT: Actually fopen would return NULL when file is not readable. My bad. Anyway, such problems are trivial to debug.
C is dead simple in that respect. If you can't write right concise code no language can help you. However of course, as C is more verbose it is more challanging to structure code well. And to some extend other language can help you move the point where control flow becomes a problem.
The biggest problem in C programming for me is memory management, but maybe it's just a lack of experience. Certainly C++ is convenient with smart pointers and automaticall called destructors.
Anyway, i didn't consider big project a scope here. I wouldn't write realy big programs in wine. I would try to create as small independent parts as possible.
Of these, Rust has only destructors :)
Understandably you didn’t put your whole effort into a small code example in a HN comment, but a language that allows one to do so without any errors would be big productivity and safety gain.
I don't write professionaly C code. I use it for my purposes so debugging is easy. I agree that such a problem in a bigger system whithout ability to reproduce environment and debug would be very nasty. However that is matter of context. In bigger system it's more reasonable to buy more safety for price of simplicity, because I doubt any of competitors in the benchmark is as simple as C.
Wasn't it? It reminds me about the old article by Strostrup on c/c++ for beginners (I seem to link to this quite often, apologies if it gets repetitive):
https://www.drdobbs.com/learning-standard-c-as-a-new-languag...
One point of using c++ over c is that it should be easier to get a program that is in fact correct - regarding things like closing files etc. (that said conciously leaking some recources ("do and die") could be considered idiomatic c i suppose).
If you can use a GCd language like Go why would you go with Rust(which add a lot of complexity) or Zig(which is unsafe(1)) instead of the numerous existing GCd languages D,Nim, Crystal, Java,... which provides memory safety without complexity
If you can't why evaluate Go instead of Ada or DasBetterC?
1: At work a frequent issue in C++ is UAF a pointer to a stack variable which outlives the function, Zig don't help you here..
Sorry but if this sort of UAF is actually frequent, I would hazard a guess that your coworkers would struggle in pretty much any language? RAII-based lifetime management really isn't that difficult, and the type of bug you are referring to isn't even subtle.
Most surveys place the use of such tooling around 11%, which is why all major OS vendors are pushing for hardware memory tagging, as by then is no way to avoid using them.
Those browsers are part of the 11% mentioned above.
Uh? This issue wouldn't happen in Java because everything is heap(GC) allocated. Which is probably why developers new to C++ have this issue.
Walter Bright said below that the D compiler find most of these issue at compile time, well that's nice for D but unfortunately that isn't the case for C++ at least not for gcc9.
@safe:
int* f(int* p) { return p; }
int* g() { int i; return f(&i); }
Compile with: dmd test -dip1000
and the result: Error: reference to local variable i assigned to non-scope parameter p calling test.f... yet every time time I've been told "you don't need Rust, just use C++", I can point to at least one UAF in their own code, and have even pointed to CVEs on Mitre for their own projects!
People make mistakes. Use tools that stop these mistakes, rather than helping you to "write code faster" like in the article (aka writing bugs faster).
- Implicit lambda capture where you use & (admittedly often out of laziness).
- A string_view constructed from an accidental string copy (e.g. if your function parameter is a const std::string instead of a const std::string&, or if you write for (auto foo : v) { ... }).
- A callback that references some member variables of some object on the stack, which usually completes before the function returns (but maybe you forget to synchronize in an edge case).
Tooling can help identify a number of these issues, but it's not perfect. And a number of these issues are very much C++-specific.
There are plenty of aspects of programming that need systematic solutions instead of just telling people to get better, but returning pointers to the stack should not be high on that list.
If people are doing that, it means they don't understand what they are doing or aren't thinking about what they are writing, probably both. It is much easier to return regular data structures.
In modern C++ it is rare that a raw pointer should be returned in the first place. I wouldn't be sure what to think if I had a team full of people consistently and frequently returning pointers to the stack frame of the function they came from.
In my head it is always said with a German accent.
And yes this got me interested in D. I always knew of its existence but for some reason I never really looked into it.
Memory leaks are possible in GCd languages and when they happens, sometimes you cannot fix them easily / properly because of architectural problems. Thinking about the lifetime of an object can help with this.
We also still miss something in many languages that specifies whether we can modify or access an object after passing it as a argument to a call, which can can problems not related to memory safety.
(I've played with Rust but I don't practice it daily. I've also played with D but don't practice anymore. I don't know Go.)
Memory leaks in enterprise java in my experience are usually things like forgetting to close a resource of some sort or maybe doing something silly like continually growing a set or map with duplicate objects because of faulty equals() or hashCode(). Both of these cases are well covered by static analysis.
Maybe the trickiest but still relatively common scenario is a design issue where a weak reference is required - i guess that fits parent description pretty well.
Go is simple, has very fast compile times, produces a single static binary, easy trivial to cross-compile, has very short GC pauses, has a fantastic standard library. The same cannot be said for those other languages.
Rust has a very strong type system that makes lots of runtime errors into compile time errors, it's basically as fast a language as you can get, except for GUIs it has a really good library ecosystem (way better than all the languages you mentioned except maybe Java), it produces a static binary, cross-compilation is relatively easy (as long as you aren't trying to cross compile to Mac).
I have not run into a case where go can't be within 2x speed of C.
And I love how zig is even closer to C, but has the ergonomics of go.
What an amazing time to be alive.
When I was learning Go years ago, I decided to go through and speed up some of the Benchmark Game's Go code. Some were on the order of 20x speedup if I remember right.
Of course, the maintainer of the site rejected it because of what I perceive as a clear bias against the language, but the point is...it's easy to fix hotspots in Go.
This was in 2013. My first submission was using memory pools for binary trees. It was rejected for using memory pools, even though the C version quite literally used mempool. I even redid it to use a 'third party' mempool library, rejected for the same reason.
I didn't even bother submitting any others.
I hope my assessment is 'fair.' I used to feel more strongly than I do now, I guess.
It's a weird situation. The rules say 'dont write your own memory pools', but new languages wouldn't have a popular library available. Seems it should be 'dont use them at all', or allow them for all. This puts any new language at an immediate disadvantage.
Anyhow, thanks for keeping it going so long. Fun little site.
"Go was born out of frustration with existing languages … To meet these goals required addressing a number of linguistic issues: an expressive but lightweight type system; concurrency and garbage collection …"
https://golang.org/doc/faq#creating_a_new_language
https://golang.org/doc/faq#garbage_collection
Seems like you wished not to show one of Go's big features?
It's pretty clear that Go designers wanted a GC, but also wanted control over memory. In go, it is trivial to create a slice of structs that's contiguous in memory, and built-ins like 'copy' means the language designers wanted users to use this feature.
When I write go, GC is used almost always. Except when it becomes a bottleneck, at which point I trivially switch to memory pools. I just add a comment like "preallocate to avoid allocating in a loop", and move on. The next person that comes by my code would have no trouble understanding what happened there--unlike Java where using memory pools stops looking like Java.
I think when the Go FAQ mentions existing pain points, it means both GC'ed and Non-GC'ed languages.
Zig is cool because it went the manual layout route, but with a pluggable allocator. I think it's a brilliant choice, especially for embedded.
The problem is not in exploits, the problem is in price of people having enough knowledge to not do stupid mistakes and every company wants developer that pays him nothing (or as little as possible) and gets everything or close to it (open source(tm); I want to build a house. I am still searching for an idiot that would make it for "recognition").
And as such they search for most fool proof technology where they could replace expensive and knowledgeable developers with, ideally, street bums that would work for food. And most of the software we are using today is like that: made from street bums.
The difference between 1990s software and today is too obvious to not notice the difference, even the "great breakthroughs" and new hypes are just xeroxing what we already had.
(No offense, doing a fair bit of C work myself)
C, CAN be a bleading edge. Depends on who is using it., based on a simple fact that there is nothing you cant do.
Except for the amount of money the companies need to pay for skilled c/c++ developer against the <name your poison> developer.
I think that in contrast of common belief, the difference in languages has nothing to do with benchmarks. But it has everything to do with price/performance. And performance is not important today (`I'll just start another "cloud" instance whatever the cost`).
So we are getting to the price. 10 monkeys for the price of one "real" developer. Why? I was reluctant to use java/c# software due to a fact that it was mostly a crap (and still is). Now I am forced to use it as there are no alternatives. The software is just another thing where the quality is non relevant to the earning. So we eat shit.
<dont_take_this_too_serious>
I think that most C programmers are just programming monkeys. People who enjoy solving sudokus^H^H^H^H^H^H^H pointer arithmetics and format string riddles and think that incrementing a variable in the middle of a statement is the best thing since sliced bread. Programmers, who do not have the necessary skills to become engineers - the latters needed to build modern systems. I think it's good that languages like C exist, so we can keep those people busy and let the more capable people work on what makes the real difference when it comes to robustness, performance, and integrity: algorithms, data structures, and software architecture. Things that require abstract thinking beyond "Where do I store my pointer so I don't forget to free that struct later?".
</dont_take_this_too_serious>
Sure and I agree. The companies have tried to use the monkeys for C development, but it didn't work. There is just too much to be aware of. All the next goals were to find a technology that would still keep "low" paid monkeys but prevent them doing stupid mistakes and train them as fast as possible to produce some results, whatever the results were.
And we got the winners that we today call java, js, c#, ruby, php, python,... and the devices that are using extremely capable cpus with vast amounts of memory and "diskspace" to produce inferior results.
It is not the point that any of those languages is faster (I love those benchmarks comparing speed of language that is written in c/c++ with c/c++), that any of those is better.
The point is that they allow companies to pay less to the developers for the same effect with a loss on speed, memory and all the other handy dandy things that can be bought on (and dumped to) consumer side.
It was never about speed, memory footprint, syntax. It was always about paying software developer the least amount possible.
And we actually helped, open source(tm) anyone? Or in other words, working for free for no benefit but dubious recognition that no one but ourselves care about?
I wonder what is any other branch of industry that relies so heavily on recognition instead of payment.
As I have mentioned, I want to build a house, please recommend to me anyone prepared to build it for free for "recognition" instead of payment...
... No?... So we are the only ones THAT stupid?
Go brings faster results due to the really great and easy to integrate 3rd party libraries. There is literally no way that I would go for such a large number of third party dependencies with any C++ software - it is just too much work to prepare the libraries for use.
The amount of thinking to make Go programs run fast and with low memory fingerprint is about the same as C++ with a huge minus that you dont have a delete/free keyword that designates "now the memory is de-allocated". With go, you need to think hard, I would prefer GC language that has a deallocation keyword, so I can forget about everything I dont care about but still deallocate what I want in specific moment.
For me personally, the hugest difference of Go against C/C++ is the ecosystem and how simple is to use 3rd party libraries/modules/whatever.
D is nice. I could see myself picking it instead of go. It's much more expressive than go and it's mature enough. Cross compilation story isn't as great though. Haven't tried asBetterC. I was under the impression this corner of D isn't as mature as D proper.
Java, IMHO, has limited uses, in the sense that making distributable binaries for client side usage (e.g. CLIs) is quite a hassle, and the need for a JRE makes it quite a bit bulkier than a C or go thing.
I'm partial to Nim and Crystal (and V). They aren't as appealing to me personally because they feel complex compared to C and go (and zig). If the appeal of these languages is "a better golang", I think D has beat them to the punch. Also, again, cross compilation tends to be a bit of a deal breaker for me.
Another language that seem interesting in this space is Odin, but I could not for the life of me find stdlib docs for it.
Another seemingly interesting language is Beef, which has a sort of C# feel (a good thing in my books), but unfortunately I haven't had a chance to play around with it yet.
Cosmopolitan is cool, but is just C, and younger than even zig.
Zig will probably never be as safe as a GCed language or Rust in this regard. But that's because it would go against other core design goals.
I feel like the future of non-GC languages is Zig as better C (dead simple and explicit language), and Rust as better C++ (complex language with more memory management facilities, etc).
The choice of languages was a bit odd. But I think Go is still in C's territory when it comes to being simple and explicit. Memory management (GC/not-GC) isn't necessarily the most important distinction in programming languages.
I don't care about that in a Python program, but I can't see why I would accept it from something that's supposed to replace C.
I also think you're imagining a lot of hard work during execution in Rust that just isn't there, most of the tricky stuff happens at compile time. If you can prove you really don't need the safety that does have runtime implications, you can choose not to have it, but of course you probably don't have proof, just the same gut instinct that usually gets us into trouble in other languages.
Macallan sold some 72 year old scotch in 2018: https://www.foodandwine.com/news/macallan-scotch-72-year-old...
'?' evidently represents "any character" per the !glob("h?i", "hi") test.
But, if I add a test for glob("???", "hi"), that succeeds unexpectedly even though it should run out of input.
I believe it's because the '?' case checks nt < text.length() (which AIUI is for consuming variable input for '*') instead of t. Changing that case to t < text.length() seems to fix that problem.
(This looks like it's the case for all four languages, as the glob algorithm looks identical in all four, modulo syntax differences)
This developers measure of productivity is "how low level can I go" clearly; they declared zig was clear despite saying this:
> The lack of string handling routines in the stdlib was unexpected, to concatenate strings one has to do everything manually - allocate the buffer, put strings there. Or use formatter and an allocator to print both strings side by side and free the buffer afterwards.
This combined with "strings" being byte buffers like in C are not a good thing. I had a quick look and the author appears to be german; how do you handle an umlaut in zig?
His dismissal of C++ as "not having a build system" seems like considering the language without the ecosystem. Using cmake and llvm solves all of his gripes on C++.
Finally, this post measures the productivity of a single developer, On a 1-3 file project. How these projects work with 2-5 developers, or on projects that support more than the anglosphere are very important.
Sure they (not sure about LLVM as a build system?) work but they really aren't all that great in my experience.
Llvm was specifically in reference to the "tooling" comment in the blog post. Clang format and clang-tidy(which conveniently has a neat integration with cmake!)
They are build systems alright, but they miss the most crucial point: dependency managment.
On the other side we have:
- cargo for Rust
- buck and buckaroo for C++ which seems nowhere near complete
Am I missing something?https://doc.rust-lang.org/cargo/commands/cargo-tree.html
Download all dependencies locally:
> I could then go to an internet facing machine and run a cargo command
Copy over your Cargo.toml to that machine, run vendor, copy the vendor files back.
It integrates with cmake too!
Last week I debugged an issue in a big C++ codebase, where we were getting memory corruption. Source of the issue? Compiler, linker and build system being separate things and only compiler understanding types (also, lack of modules, which is another symptom of build system being separate from the language). Namely, conditional compilation was involved, someone made a struct which had a field in it based on whether a preprocessor constant was defined or not (passed with -D to the compiler on the command line). Turned out some files were compiled with the define and some not, so some files were compiled based on the assumption that this struct is N bytes long, and some with the assumption that this struct is N+M bytes long. But the linker doesn't have any information available to it to detect this error.
And then there is the issue that to write a C++ program these days, it doesn't suffice that you just know C++. Languages you need to know:
- C++
- language of your build system, which is always handicapped, yet Turing-complete
- Python
- if something goes wrong, and you need to debug something: Make with its custom shell-language
- shell
It's a tower of babel. For all the whining on users of other languages for using libraries, C++ users have a lot of complexity hidden in their build systems.
In Zig and the not-yet-released Jai, you only need to know the language you write your program in. The "if" in your language of choice is perfectly suitable for expressing conditional compilation. Structs in your language of choice are perfectly suitable for expressing declarative configuration.
This has not as much to do with the loosely coupled build system complexity you have to scaffold in a c++ project, more issues in the core language itself.
No offense but this just sounds like bad design. Haskell has the C preprocessor so technically that particular criticism would apply to it too, but nobody would say it's Haskell's problem just because in theory people could use this feature to write terrible code. (I understand maybe this approach was necessary for interfacting with some other vendor/legacy code somewhere, but in that case the fundamental issue is still bad code, not C++: the bad vendor/legacy code).
I'm not sure what you mean by that. Are you saying you can use #ifdef, #include etc. in Haskell code?
> No offense but this just sounds like bad design.
I agree. However that's just one way you get multiple definitions for the same struct. I think a language should detect such an error. It will detect it for functions. If linkers did not detect multiple function definitions, would you shrug it off by saying "having multiple function definitions is bad design"? (I'm asking in a non-accusatory way. I'm not sure how to word the question more gently.)
Many of us don't have the luxury of choosing the quality of code we're employed to debug. It would be nice if languages had some rudimentary sanity-checks to, well, preserve our sanity.
Yep: https://guide.aelve.com/haskell/cpp-vww0qd72.
>Many of us don't have the luxury of choosing the quality of code we're employed to debug. It would be nice if languages had some rudimentary sanity-checks to, well, preserve our sanity.
I agree, I don't think C++ is especially bad in this regard. If people are going to write bad code, they can write it just as well in Java, C#, Python etc., they'll just use different language features to bring about the horrors. No language (apart from maybe Go, in its extreme simplicity) can save us from having to debug bad code.
I think they avoid this issue by using a config header file that contains the defines, so the compilation unit either compiles with the config header, or if it's not included and config macro is used, compilation fails altogether.
It's still possible to include different config header than you're expecting, though. But that's easier to defend against. (you can just include a config header directly via cmd line without using #include <...> in each compilation unit)
It's also better to use #if MACRO = 1 instead of #ifdef MACRO, because the first one will fail if MACRO is not defined.
The problem of C strings is that they are zero-terminated, not that they are byte buffers. Strings in Zig are foremost slice types (pointer/size pairs), but for C compatibility, Zig also has the concept of 'sentinel terminated arrays':
https://ziglang.org/documentation/master/#Sentinel-Terminate...
> how do you handle an umlaut in zig
Via UTF-8 encoding, as it should be.
> cmake...
I use cmake for my C and C++ stuff too because it's the defacto standard, but Rust's cargo or Zig's integrated build system are on a whole different level.
Agreed, but now your standard library doesn't have string support, it has byte buffer support. That's no improvement over C, compared to rust.
Apart from that, Zig's string processing functions are not that bad, what's unexpected is that they are in the memory-operations part of the standard library, not in a specific "string module" (after all, the Zig compiler is (being) written in Zig, and the parser needs to do a lot of string-munching, so I guess we'll get more powerful string processing helper functions in the standard library over time).
But if I need to do lots of high-level string processing I probably wouldn't chose a systems programming language to begin with, languages like Python are a better match for this.
"Handle" how? If you're just shuffling bytes around, you don't need to worry about it. If you need to iterate through by codepoints, there's stdlib Utf8View module, though I understand this aspect of the standard library is set to change before 1.0.
The fact that multi-byte codepoints don't have bytes that could be ASCII gets you decently far for common things like splitting a string at a newline, or otherwise matching against a char.
edit: actually, were you talking about the code in the post? Looking at it now, I see it's buggy in this regard, if you're wanting a ? to match ü. I think basically what you'd do is in the branch corresponding to the ? you could use the stdlibs's utf8ByteSequenceLength function to skip the text ahead that many bytes, while advancing the pattern by just the one byte.
-------
"Improved handling of strings and unicode", https://github.com/ziglang/zig/issues/234
Andrew answer:
> I'm not convinced that this is a language change rather than a standard library feature.
(source https://github.com/ziglang/zig/issues/234#issuecomment-28927...)
-------
"Proposal: Add String to the type system", https://github.com/ziglang/zig/issues/7734
Andre answer:
> We won't have a string type in the language.
> OP problem can be solved with a better std.fmt.format API which better communicates intent. This is a deficiency of the std lib, not the language
(source https://github.com/ziglang/zig/issues/7734#issuecomment-7581...)
It depends what the problem you're facing with. If the problem is "people writing C++ code with terrible latency characteristics because they don't understand how it works underneath and the stdlib doesn't even provide a way to efficently concatenate strings (i.e. by using a buffer, not allocating new memory)", Zig's approach is a great thing.
About documentation, its still not 1.0, the language. So, its a work in progress.
Zig is kind of in between Go and Rust in terms of the learning curve. In terms of programming, it is in between C and Rust. But it is simple enough. The documentation as I said for the stdlib isnt that great right now as I said.
A few examples:
- Json parsing being special cased into the language
- multiplying a user-input integer with a time value to be a timeout
- channels and goroutines (sorry, I've been spoiled by erlang)
Aside from things that are actually bugs, I haven't found anything like this in Zig. On the contrary, I typically have found something that doesn't work how I expect, and then I think about it, and it's "of course it's like this, why isn't C like this??".
Not sure how long the author spent looking at zig stdlib, but there is std.mem.join for this. Most string utils are under std.mem, it seems. To be fair, I also rolled my own std.mem.eql for string comparison before stumbling upon it as a beginner, so this definitely an area for improvement.
Overall I agree with the assessment about stdlib docs. Many things are not even listed in docs, and many that are have little to no description. Also some of type signatures are not really clear, e.g. when they use `var` as a type.
What works for me is having stdlib docs and source code from github side by side and jumping back and forth between them.
But overall, as someone who's gone through a similar language evaluation process, his assessment feels pretty on point: zig's lack of maturity still shows in a lot of places, Rust feels complicated and puzzling at the beginning, etc. My only difference is I'd lean towards C if I had to pick between it and C++, but that's largely a matter of personal preferences IMHO.
To reiterate: that doesn’t mean this is bad or wrong. It might be a bit harder or more complex than it has to be. But also, you have to know that these things exist in the first place, which is it’s own challenge.
EDIT: Oh look, someone on lobsters thought the same: https://lobste.rs/s/ndxi2r/better_c_benchmark#c_k2kwjs
Write code:
1. In less time
2. That runs faster
3. That's less buggy
Pick one.
... this article advocates for (1).
Strange conclusion considering their benchmark was I/O-bound by design
Attention to detail comes with the territory for programming roles. People who can't or don't want to pay attention to details are not fit to be programmers. We are working with precise binary digital machines, FFS. I sometimes find this attitude among students of my training courses - the misconception "computer, do what I mean, not what I say". Obviously such students don't do well in my course or in their career.
That is where i stopped reading. If you're not using an IDE in 2021, you're not working on serious software/big codebases/commercial projects and what you have to say is not something I'm interested in, ESPECIALLY when talking about UX of programming! VIM, oh please... time to have a nap, grandpa.
System.gc()Vim is a fully featured IDE, learning to use it efficiently can greatly increase your productivity, and most important: it's also fun.
> VIM, oh please... time to have a nap, grandpa.
Such disrespect, all that's missing from your snarky comment is "ok boomer"...
So, some of you who rely on the IDE all the time maybe need to do without it a little to train some muscles that have atrophied.
If you want to read https://news.ycombinator.com/newsguidelines.html and commit to using HN as intended, you're welcome to email hn@ycombinator.com and we'll be happy to unban you. Otherwise not.
> His pretty buggy code infinite loops if pattern == "". Does anyone truly believe his conclusions, if he evaluates programming languags according to how well they execute buggy code? -systemBuilder
Perhaps it's the other way around and manual buffer management is actually the complex thing, since your application logic will have to involve a lot of code doing toil that isn't directly related to what you want to accomplish.
EDIT:
To expand a bit, I'm a huge proponent of simplicity; I think overabstraction is a massive problem with software nowadays. However, my thinking is that the problem isn't so much with the abstractions themselves, but that they aren't transparent. ORMs are a frequent offender here: a good abstraction wouldn't allow you to perform a thousand database queries by accident just by looping over a list. You could still model a list of database-backed persistent objects as a list, but any operation that hits the database should be an explicit fetch, not an implicit one.
Less accidentally quadratic algorithms, this way!
Maybe you need to iterate over two differently sized datastructures in lockstep, or over a complex nested structure, or accumulate and filter the values, or load the data into a temporary buffer that gets flushed and reused once it fills up, or maybe your data source is infinite, or all of the above.
These are cases where using iterators is significantly safer and notably, more composable.
LOL at "proper iterator abstractions" to replace loops. You will take loops from my cold, dead hands!
At the end of the day, all things are.
In the domain of programming languages, minimalistic low-level languages like assembly, C, and Forth, tend to be unsafe. They enable categories of serious bugs that never occur in safe languages. Modern garbage collectors, for example, are very complex and very valuable.
One of the reasons Safe Rust is so compelling is that it seems to succeed in offering safety, C-like performance, and good developer ergonomics. Prior to Safe Rust you had to pick two: C and C++ lack safety, Java/C# lack performance, and formal methods are hard to use (at least in the current state of the art).
Safe Rust is not a simple language. It's not possible to achieve what Safe Rust does without sophistication far beyond that of C.
But not all of that complexity is good. For example, things like branch-prediction vulnerabilities exist as a result of that complexity, which makes it extremely difficult to reason about the entire system and predict which interacting systems might lead to security issues.
And it looks as if some of the complexity of the x86_64 instruction set is a liability with respect to performance compared to "simpler" instruction sets like ARM.
Also with regard to Rust, the more that I write, the more that I wonder if some of that complexity is due to unsolved design problems in the core of the language. For instance, once you start introducing data structures with references inside, this starts to introduce all kinds of complexity, which baloons when these kinds of types start interacting with other systems like async.
I am not expert enough to know what the solution would be, or to know if these types of problems really could be avoided, but Rust sometimes feels like a language which is missing that one key piece which would keep all of this complexity under control.
I don't think any of the issues with Intel's x86-64 chips are due to the instruction-set, they're due to the chips' internal architectures. ARM CPUs are by no means 'safe by nature'.
> the more that I write, the more that I wonder if some of that complexity is due to unsolved design problems in the core of the language
> Rust sometimes feels like a language which is missing that one key piece which would keep all of this complexity under control.
I'm reminded of a quote [0] from Bjarne Stroustrup, creator of C++:
> Within C++, there is a much smaller and cleaner language struggling to get out.