Great write-up on the author's personal perspective. This part in particular I think is a good summary of why this works for him:
The next reason is that during my time with C, I have developed a custom software stack designed around memory safety.
[...]
In other words, I am not using C; I am actually using the partially memory-safe Gavin D. Howard dialect of C.
In other words, don't try this at home? I definitely should not try it at home.
> I can write C that frees memory properly...that basically doesn't suffer from memory corruption...I can do that, because I'm controlling heaven and earth in my software. It makes it very hard to compose software. Because even if you and I both know how to write memory safe C, it's very hard for us to have an interface boundary where we can agree about who does what.
What a clear way to describe the difficulty with manual memory management.
So like Lisp, C might end up being a solo programmer’s tool too. But not because it is a breeding ground for DSLs but because everyone is adhering to different conventions in the absence of the compiler enforcing one.
I don't think that the composability/interface-boundary/memory-mgmt argument stands for C and much even less so for C++. There's nothing difficult about it nor ambiguous.
Did you actually watch the linked presentation? If so, I would be curious in an earnest rebuttal: I very much stand by my conclusions -- and even more so after having spent the years since on Rust-based systems and C/Rust hybrids. Specifically, I have not seen a BTree implementation in C that I would consider to be composable; can you point me to one?
I did not watch the presentation, sorry. Depends what the definition of composability you have in mind so if you share what you mean by "composable BTree" implementation, I would be happy to give my opinion. I ask because there's no much I can see about the composability of a b-tree implementation other than supporting multiple types, and this is certainly doable although not as elegant as in C++.
While I appreciate your candor, you may want to know what argument, exactly, you are putatively refuting? (Specifically: the argument is not about types -- it's about ownership.) If it's not too much to ask, take two minutes to watch the linked clip. As for the composable BTree implementation, I actually am not asking for your opinion -- I'm asking for a link to the implementation itself. The details here matter (and indeed, that's the whole point).
I watched the video now and it is literally what one of the parent comments paraphrased and what I was responding to.
What you said is:
> I can write C that frees memory properly...that basically doesn't suffer from memory corruption...I can do that, because I'm controlling heaven and earth in my software. It makes it very hard to compose software. Because even if you and I both know how to write memory safe C, it's very hard for us to have an interface boundary where we can agree about who does what.
And I stand by my point. Single-responsibility principle will get you covered, yes, even in C.
> I actually am not asking for your opinion --
You could show some respect.
> I'm asking for a link to the implementation itself. The details here matter
B-tree btw is a silly example which particularly is not among the hard design problems out there and there are dozens of implementations laying around. Obvious examples to look for would be in transactional database systems implemented in C.
> (and indeed, that's the whole point).
... and the point not being quite clear, even after politely asking for a clarification, so perhaps next time you don't try to play the authority but try to provide an actual example to support your otherwise questionable claim. I'm sure Rust community will profit from individuals like yourself.
I saw it expressed by multiple people whose language of choice was Lisp but the only one that I specifically remember is Ron Garret’s recent post called Lisping at JPL Revisited[1]:
All this is a reflection of the so-called Lisp curse, the fundamental problem with Lisp -- its great strength is simultaneously its great weakness. It is super-simple to customize Lisp to suit your personal tastes, and so everyone does, and so you end up with a fragmented ecosystem of little sub-languages, not all of which (to put it mildly) are particularly well designed.
He doesn’t specifically say here though that this makes Lisp a solo tool. I can’t find a source for that right now unfortunately.
Lisp is the most powerful and elegant language. That is a problem because you're giving weak, fallible monkeys phenomenal cosmic powers. Now, many monkeys are well-behaved and disciplined, so they create reasonable, pleasant codebases. But some monkeys go bananas.
Which is fair. The author also highlights an interesting problem: Some of us will opt to not use Rust, even when we should, simply because the language doesn't appeal to us.
I love the idea of Rust, but find the syntax to be just awful, and the entire thing just overly complex. I'm not saying that Rust isn't the way it is for good reasons, but the code is often hard to read. Some of the syntax choices are in my mind questionable and serves only to have Rust programmer appear smarter than the rest of us.
Rust shouldn't try to accommodate people like me, because it will make everything worse for everyone. Instead I should look elsewhere, may not to C, I'm not that clever, but to languages like Go, Nim, Crystal or Hare. It's important that we have choices, because even the best designed language, and I do consider Rust a well designed language, will fail to appeal to everyone.
The language is just an interface between humans and the machine to make it easier for us humans - machines aren't all the same so why should humans be?
"Some of us will opt to not use Rust, even when we should"
Nobody SHOULD use Rust.
Some may use it, but nobody should.
And because of this "should" many will not use it, especially when you consider that the language is conceptually overweight and there was already one "very popular" conceptually overweight Scala language, which now only the old people remember (laughs).
You need not be so offended, it's not even about your text.
You can use search for the next criteria: why you should use rust
This informational noise is very disturbing and forms a somewhat distorted perception of reality, because it profanes the choice of a very complex instrument (which is just a small part of the huge instrumental set). And thanks to this noise, the obvious boundaries between the apparently different "must" and "can" are lost.
Yes, that was very good read and help moderate my annoyance with Rust. I could quite figure out if the "unfolding" or rewrite of the examples had any negative impact on performance. If it doesn't I fail to see the benefit of the rather convoluted code used in the examples.
I wrote a few programs in Nim to get a feel for the language, coming from a background in mostly Python I found it pretty easy to pick on the basic language structure.
Seriously. The post should've just been the "Rust Isn’t Fun for Me" section. That's the reason, everything else is just rationalizing.
To be clear, I don't mean to disparage the author's decision. Programming can be an artistic endeavour, where personal enjoyment trumps most concerns. If programming C is what keeps you motivated, go ahead and use it! Just be aware that this may make your software risky to use and rejected in many real-world situations.
The "Rust Evangelism Strike Force" isn't actually a thing that exists. From my understanding the term was created as a self-deprecating meme by some Rust users to describe other Rust users. It's not a core policy and basically all serious Rust users don't do any of the alleged activities claimed to happen. They're too busy actually writing software.
Er, it's not a formal thing, but it's absolutely an emergent phenomenon that exists. Every single discussion on HN about either software vulnerabilities or C will have comments asserting that everything must be rewritten in rust. I would believe that it's not from "serious Rust users", but someone will show up to say it.
> The "Rust Evangelism Strike Force" isn't actually a thing that exists. (...) It's not a core policy and basically all serious Rust users don't do any of the alleged activities claimed to happen.
If that was remotely true how do you explain the Actix episode?
Some over zealous Redditors doing Redditor things. That's where it started anyway. There was no coordinated effort. Probably some people from 4chan as well.
> Some over zealous Redditors doing Redditor things.
Except that this was not a Reddit problem, was it? This was a problem created by the Rust community that showcases exactly the "Rust Evangelism Strike Force" behavior we're talking here.
Would it made it a Mastodon problem if the Rust fundamentalists opted to use that to drive their relentless attacks? It wouldn't, would it?
I mean, it surely wasn't a GitHub problem when these Rust evangelists started harassing the maintainer through bug reports, which was the place where it all actually took place.
By "people threw a fit" you mean the Rust community put up a relentless harrasment campaign targeting the project author, even to the point where they involved their employer, and ultimately forced the maintainer to give up on FLOSS and abandoned the project altogether.
I mean I saw it on Reddit, guy got panned for abusing unsafe and bunch of unsafe were patched out. That's it as far as what I am aware of.
> Rust community put up a relentless harrasment campaign targeting
Don't judge community (especially one you don't know) by their worst member.
Rust community didn't all agree and made a pact to harass/dox that guy. Few assholes did.
Trying to generalize this behavior to entirety of Rust community is disingenuous. Imagine if I started saying all programmers were wife killers, because some programmers killed their wifes.
> Don't judge community (especially one you don't know) by their worst member.
The community was massively represented in that harassment campaign targeting Actix. We can still read the threads and the bug reports that fueled that shit show. Things got so bad that distinguished members of Rust's core team felt compelled to put on their PR hat to work on salvaging Rust's reputation and do limitation.
It was not bad apples. It was a hallmark of Rust's community.
I don't believe in collective guilt. If 10 or 20 or 200 assholes out of group of 30000 do something stupid, do you sentence the 30000 or the assholes, even if the rest of community has distanced from it? You are generalizing actions of a vocal few to the entire group.
Also, sure Rust people care overwhelmingly about Rust crates.
Any large enough community will statistically have some percent of "bad apples". You're arguing as if those bad apples are a majority. And not a statistical footnote.
> I don't believe in collective guilt. If 10 or 20 or 200 assholes out of group of 30000 do something stupid, do you sentence the 30000 or the assholes, even if the rest of community has distanced from it?
You mentioned Reddit. Rust's subreddit had at the time over 80k subscribers. The subreddit massively piled on the maintainer of Actix. This harassment campaign was so massive that core Rust members felt obligated to write public declarations how Rust should invest in community building and, with the Actix case as an example, whether they could actually reject contempt culture.
It was not an isolated bad apple. Rust's community is renowned for being toxic, hostile and abbrasive. This topic is underlined time and again even in HN. It's not possible to hide this fact.
I don't think people ending up on the wrong end of it particularly care if it's an actual organization or just an emergent "feature" of doing things in a space somewhat adjacent to Rust.
It was actually coined by the n-gate guy as a description of how some Hackernews descend upon a thread to advocate for Rust, typically proposing that something be rewritten in the language because muh memory safety.
The Rust community just picked it up. as self-deprecating humor.
I like C as well, but I never kid myself into thinking that discipline and documentation can replace compiler restrictions. I've written large codebases in C, sometimes alone, sometimes in a team, and despite DECADES of experience and meticulous attention to detail, I STILL find the same old memory errors in my own code.
I mean this is demonstrably untrue: SEL4 is written by mere mortals, in C. It's safer than rust, there are formally verified security guarantees that include and go beyond the memory safety of rust.
It is the case though that c is an incredibly shitty language to do this sort of static analysis on because for example it's easy to do shenanigans like lexical macros that make the static analyzers job much much harder.
seL4 also has the whole Isabelle/HOL theorem prover to verify its properties, so it's not quite right to claim it's written in C. If you can claim seL4 is "written in C", you could equally claim that if the Rust compiler emitted C, you're writing a C program -- after all, a static type checker is just a shittier theorem prover.
This is like saying <insert language with docstrings> is not actually writing in <lang> because you need whatever engine's target language (md, XML, whatever) to compile the docs.
That comparison is nonsensical and you know it. You can delete all the comments and have the same guarantees about what the code does (none). You can’t write a kernel without some formal verification tool and then claim that it adheres to the specification.
Oh, I see. You said that it is “written in C”, and then later denied the claim that it is also written with that formal verification language. If you delete those annotations it will still adhere to the specification,[1] but it’s still false that it was purely written in C—your statement that “there are formally verified security guarantees” in fact relies on the existence of that proof code.
But fine. With that kind of dishonesty I could claim that the Linux kernel is written in nothing: I have compiled it and deleted the source code. Look ma, it’s no-code kernel.
[1] Kind of a bummer though that you cannot change any of the C code any more now that the verification code is gone.
Jesus Christ. It's not like most c programs arent already written in another language like make or cmake. By your standards none but the most trivial of hello world a.out c programs are c programs.
Macros are not such a big deal: static analyzers do not work on the source code directly, they work on some intermediate representation usually (often the LLVM IR, for Ikos and Clang SA for example) or parsed source code. Either way, the pre-processing has been done when the analysis starts.
Funky macros can still make static analysis more complex by introducing layer(s) of indirection of course.
You can make any code in any language "safe" when you throw enough resources at it. That's not an argument for safety, nor a rebuttal (unless your goal is to prove someone wrong on the internet).
It's the exact opposite of "you cannot make language X safer", only if you define that as "given enough time and resources, you could make a proof that your program is correct".
But that's not a safe language; that's a safe work. ANY programming work - regardless of language - can be made safe via such expensive means, which puts this square into the space of "argument ad absurdum". You could argue that brainfuck or assembler can be made safe via these means, but everyone would rightly ridicule you for it.
Maybe I'm crazy but "being a god" is much higher bar than "given enough time and resources, you could make a proof that your program is correct". Moreover, unlike your bf analogy: Sel4 exists, and is in use, and is used in safety-critical applications such as military drones.
it's also a very limited version of C, without concurrency, dynamic memory allocation, or goto, and you can't even take the address of local variables!
To be fair, religiously running Valgrind, Helgrind, Asan plus a static analyzer will probably do more than most compilers to ensure you don't have memory issues. Probably more than Rust, at least if you find yourself needing some unsafe blocks in your own code or dependencies.
Broadly speaking, it's automated generation of test inputs to try to find bugs. For example, fuzzing a parser might involve feeding it random inputs and watching for crashes, hangs, etc.
Fuzzers frequently also use instrumentation to try to figure out inputs that will hit more code paths in a more intelligent manner.
Nothing is stopping you from running all of that on rust code though. In fact, you probably should just for sanity. Compilers have bugs. Libraries have bugs. At the lowest levels there’s unsafe somewhere anyway. Rust doesn’t remove the need for those things as good engineering practice. It just makes sure that the danger of failing to do so is minimized. Also, when there is a failure, it’ll be limited to very specific code paths (barring compiler bugs which themselves aren’t thaaat common all things considered).
Does Rust stable support the commonly-used sanitizers (ASan, TSan, UBSan, etc.) though? The last time I've checked it was still a work in progress
(https://github.com/rust-lang/rust/issues/39699).
It’s not stabilized yet but it runs fine on x86_64. I don’t really see why it not being in stable is a huge blocker. It’s a bit inconvenient, but you can always rebuild your project with nightly, no?
That's beside the point. The OP was claiming that the article author is showing hubris, and I was just pointing out that, assuming their claims about what tools they use are all true, they are in fact showing more care than any project which relies only on the compiler and runtime system for memory safety.
This! If you really care about security you should educate people about how to use available tools to analyze C instead of relentless Rust evangelism.
People arguing that "you can not ever write safe C" might also come to the conclusion that one "unsafe" somewhere in your Rust code potentially means that the whole code base is immediately at the same level of implicit memory safety as C.
> People arguing that "you can not ever write safe C" might also come to the conclusion that one "unsafe" somewhere in your Rust code potentially means that the whole code base is immediately at the same level of implicit memory safety as C.
No, the forced use of "unsafe" in Rust for some kinds of safety mechanisms to be disabled makes the code to audit easier to locate and reason about.
IF the code in the "unsafe" block does not actually isolate the "unsafe"-ness THEN conclusions can be drawn about the rest of the code.
Contrast that to C where the code introducing unsafeness, UB and unsoundness could be basically anywhere instead of a convenient "rg/grep unsafe" away.
I don't agree at all. Developing in Rust (or other memory-safe or mostly-memory-safe languages) is going to be orders of magnitude easier than writing things in C and then running through several dynamic and static code analyzers and quieting the mountain of false positives you'll see with those, plus the massive test suite that you need to make any use of them.
I was just pointing out that, in the case of the particularly diligent OP, he is probably getting an equivalent level of safety - for much more effort though.
To me writing C is orders of magnitude easier because I almost instantly can see in my mind the resulting assembly code generated by the C code. In comparison Rust feels very opaque (e.g. A simple writeln expands into a rather large macro and who knows what the compiler is doing with that). I'm sure with more experience you can get better but why bother.
Even with just static analysis and some sane coding conventions you can get C on the same safety level of Rust. I see Rust as a huge waste of everybody's time and resources and far too opinionated in many regards.
Your experience is obviously your experience. However, I don't think for the majority of people (and definitely not for myself) knowing what assembly results from my code is any way an aid in checking if it's correct or at least secure.
Either way, x86_64 assembly code is a high level language that gets interpreted by the CPU at runtime, so not sure how relevant it is to anything low level anymore.
Most of those tools check runtime properties of your code. If your test suite is perfect, that gets you quite far. I don't know of many nontrivial projects with perfect test suites.
Without a good test suite, there are often security issues that don't depend on memory safety violations. Also, I did mention a static analyzer among the tools, which will likely be able to do analysis quite similar to the Rust borrow-checker (but with an order of magnitude more false positives, which the OP claims they are fixing anyway).
This only works if you don't stray too far away from the "typical" C. But a lot of large projects written in C will have tons of extensions and modifications making tools like Valgrind irrelevant.
For instance, I worked in a company that rolled its own coroutines in a way very similar to how they work in Go on top of many other things they did to the language runtime. Valgrind couldn't "understand" what was happening in that runtime at all and if you tried to run it would spew millions of warnings (similar problem you'd have if you used Bohem GC with your C program).
There was a way to "teach" Valgrind to understand what was going on in that runtime, but and even though we tried, at some point we realized that it would take too much effort to do so.
I'd bet that OPs home-made alloca() would have a similar effect on Valgrind too.
I was only trying to defend the article author from the accusation of hubris. If all of these tools are indeed used until they show no errors at all, not even false positives (as the article claims), and if the test suite used to run them is non-trivial, then they are likely far more secure than a typical program written in Rust or even something like Go or C# or Java (which all allow and internally use raw pointer manipulation for things like interop).
> C cannot be made safe because you are not a god.
Oh god yes. That should be tattooed on everyone who thinks otherwise. “Ha! Unlike all those other people, who are clearly not as attentive as myself, _I_ never make this type of mistake.”
The author doesn't seem to disagree with your view:
> The final reason is the only one that justifies my decision: my code will be rewritten in a memory-safe language. [...] It’s my own language. It’s called Yao.
> This is the only reason I could justify that decision. All of the other reasons above are excuses. This is the real reason.
> You can bet that I do not want to accept liability for a project written in C. It will be rewritten as soon as possible.
Yikes. I do not think you have thought through the implications of licensing (or purporting to license) under the union of three contradictory licenses.
I didn't read anything past that, because this is very much a "only way to win is not to play" kind of situation.
Title and content dont really match do they? The article itself is basically this guys journey with C and the steps he takes to get something other languages now build in - ironically I think it was a stronger argument to write everything in Rust than any 'rust evangelism strike force' has put together.
Neat article, always interesting to take a peak at some ones highly idiosyncratic process.
Yes, like enjoying wood work == stockholm syndrome, why not go to Ikea??
Also to gp, title perfectly matches, pointing out why he believes in memory safety when using C, everytime also pointing out the flaws with his thinking, and why this only applies to him in his completely owned projects. Not sure what's wrong with the critics here.
C is fun - there's elegance in writing good C code and the mental model you need to understand it is much simpler than the higher level languages.
It's also fun to write data-structures yourself instead of being a mere user all the time. In C you can tell yourself that your take on how to do datastructures is worthwhile and unique (might be a slight stretch) but in other languages almost anything you can do is redundant.
I think people use the word "simple" to mean different things. You mean "easy to do," while other people mean "easy to understand what's happening." C lovers love the second definition of simple.
You only need to know, by heart, _a_ list (even a subset) of the documented behaviour of C, you can simply refer to the standard whenever you happen upon something not in your list of known good (or known undefined) behaviour. It's entirely possible to, assuming you can keep this up honestly, write perfectly well defined C without hitting undefined behaviour. It is equivalent to writing brainfuck in a language which is a strict superset of brainfuck. As long as you pick a turing complete subset of C to know the semantics of off by heart, you can write perfectly safe C (the only obstacle being human fallibility). The idea that you need to learn all the documented undefined behaviours in C is a myth and is, in fact, fundamentally wrong on the basis that the documented undefined behaviours in the C standard are only an infinitesimal subset of the set of undefined behaviours in C given that anything not explicitly defined by the C standard is automatically undefined.
If you have a mine-field with an uncountable number of mines, knowing the locations of 200 mines won't help you cross the mine-field safely. If, instead, you learn how to spot areas which are known to be safe, you can, assuming you don't make a mistake, at least attempt to cross it safely (and if you fail to cross it, you can go back and learn how to spot other areas which are known to be safe).
That is the difference between your claim that you need to know all the documented instances of UB and my claim that you just need to know enough defined behaviour to write your program.
It has nothing to do with security assessments or pentests.
“C lovers” love the appearance of understanding what happens. At the end of the day they still feed their programs to awe inspiring optimizing compilers. Sure, they can probably explain what any snippet of code in their program does, but they can’t tell what, if anything, from that snippet is executed by the machine.
I used to write loads of C, and recently I came back to write some C bindings to a Rust library, and it was _not_ fun. It was dead simple from the C side - mostly just a few functions that constructed or handled opaque pointers. The problem is the error handling is atrocious, and that was so hard to present neatly. The consequence is an overly verbose API in which I've essentially manually monomorphised in C every result. For sure, it's not idiomatic C, but it is properly handling errors, which arguably is one of the main failures of idiomatic C.
It's not just the API either. So once you've got your "nice" error handling API, you have to manually handle resource clean up - no branches that exit for you; every function call is accompanied by a result check and a goto to cleanup code (so your pointers need to be defined properly prior to any calls and initialised to null). Honestly, I don't know I ever lived with this mess. It was a painful and laborious experience to get to something that was just about ok.
> So once you've got your "nice" error handling API, you have to manually handle resource clean up - no branches that exit for you; every function call is accompanied by a result check and a goto to cleanup code (so your pointers need to be defined properly prior to any calls and initialised to null).
I have a stack allocator that will release everything allocated in the function on exit, and I have macros to replace my use of the return keyword that also deallocate everything in the stack allocator before returning.
So my code actually has code cleanup on return.
C does not have to be laborious. Maybe the fact that I have done things like that is part of why I like C?
Where can I can get such a stack cleanup system? Also, can you call arbitrary clean up code (this is hardware that needs cleaning up as well as structures allocated in rust that need to freed in rust)?
I use a stack allocator that knows about setjmp()/longjmp(), functions, scopes, and destructors. (Destructors are a function pointer type in my code.)
Basically, you tell the stack allocator to store a jmp_buf, then you setjmp() on it. Then keep going.
For a function, a special destructor is used as a marker. Same with scopes.
The same is also true of a setjmp() destructor, which is actually what activates longjmp().
The stack allocator should have three functions to unwind itself until it reaches the end of the next scope, function, or jmp, respectively.
Then, when you need an exception, or to exit a function or scope, you call the correct stack allocator function to clean up everything to that point. Then you return or whatever.
This can run arbitrary code on cleanup for an item if you write a "destructor" with that arbitrary code. For example, one idea I had was to use the stack allocator to ensure a mutex or other lock was always released. This would be done by storing a pointer to the mutex and writing a "destructor" that unlocks the mutex when given a pointer to the mutex. Boom. Scope-based mutex unlocking.
The point in Rust and C# and Java and others is that memory safety is either mandatory or opt-out instead of opt-in. This means you have some guarantees about the whole ecosystem.
Of course you can augment a language with libraries. But you are one developer and everyone else will make different choices.
> This means you have some guarantees about the whole ecosystem.
Sure, you are free to make this sort of claims. But they are besides the point, aren't they?
The cliche being discussed here is C's hypothetical ties with memory safety issues, and here we are commenting on a discussion on how someone writes C using a framework focused on memory safety and thus providing "some guarantees" about their whole ecosystem. Yet, we're still seeing comments mindlessly parotting myths in spite of the facts that are being dangled right in front of their nose.
> so the article being an account of the guy's journey using C seems highly appropriate.
More importantly, it goes against the cargo cult narrative that the choice of programming language is the one and only factor determining memory safety, and consequently any use of languages such as C automatically leads to security issues.
It's not a coincidence that we have people in this discussion coping with that concept by spinning the use of frameworks focused on memory safety in C applications as somehow representing writing code in an entirely different programming language.
Even the author mentions that he intends to rewrite everything he ever writes in some memory safe language he's building.
Despite the authors borderline obsessive efforts to write memory safe C code, it's interesting to note that his implementation of "bc" prominently contains a file documenting memory issues in previous versions. If that's what borderline obsessive gets you, how are us normies who are only programming to pay the bills, who have to work with other people who also just want to finish what they are doing or interact with blobs of code written by other people with less exacting standards supposed to write memory safe code?
Yup, that's what I thought was the most interesting part of this, how many memory errors this guy has already admitted to releasing into production in this one project despite all his precautions.
If you wanna write your hobby programs in C because you think it's fun, then fine, you do you. But this seems like a pretty good indication that basically no C written by anyone can realistically be truly safe.
That was my favorite part - it was incredibly virtuous behavior to lay it all out like that. The article would not have been interesting if it had omitted those details.
Literally, yes. But I think most people come in expecting the author to justify the choice, otherwise this is no more interesting than "why I turned left at the intersection" or "why I have blue window shades".
If the author's reason is "because I can get the same features as Rust with much more effort" they've failed to justify it and thus that reading.
I feel like you are missing the point. Using glibc in avionics software is insane, as is any other application software library. In other words, it’s asinine to pretend C is somehow more reliable because it’s used in safety critical applications. There simply isn’t anything in common with the C running on your servers and the C running on an avionics package.
I don't run servers much, I mainly write for critical applications (a decade writing for geophysical airframes, telling pilots where to go and where the pylons are when 80m off the deck for millions of line kilometres, lot's of other stuff going back a long way).
> Using glibc in avionics software is insane,
As I said above .. there's no need for glibc et al.
> I feel like you are missing the point.
We seem to be in agreement here.
> In other words, it’s asinine to pretend C is somehow more reliable because ...
What most people call "C" is (preprocessor) + (actual language) + (stdc function library).
What I call C (and hey, maybe that's just me) is just the language component, the "below the fold" library spec in the ANSI Std is easily disregarded .. it's more of a dated "proof of concept" of (for example) one of many ways of handling strings.
C is a nice language - the preprocessor and stdlib have issues.
> As I said above .. there's no need for glibc et al.
Christ, I wrote it as an absurd notion in the first place. This is honestly like writing “go ahead and wear underwear on your head” only to have someone tell repeated tell you that isn’t necessary. Well, no shit.
Really? That read to me a a self aware, even humble recognition of personal shortcomings. But I see communication as inherently more difficult than keeping an idea in one's own head.
I have a similar issue. I like the memory safety in Rust, but I don't like Rust: it limits my productivity for two reasons. 1. In order to reach memory safety it add abstractions that don't make programming simpler, but harder. 2. It is a language that started to enter the slippery slope of too many ad-hoc features. And given that I work alone, I try to write safe C code as much as I can (it is impossible to reach perfection, anyway), and avoid Rust as much as I can. What I will do more, however, is using more other safe languages when the C low-level control is not needed. Go is a good option, for my tastes.
P.S. I see in realtime, reloading the post, the number of downvotes received (the score goes up and then ways down with the downvotes). Another problem is the Rust community. It has a very peculiar way to stay in this world.
One is a cultural matter: A design idea that no longer actively attempts to produce a language with few orthogonal features that work well together. The second is a side-effect: the more features you have in a language, the more complex new features will be to accomodate all the past cases.
If you are part of development community, you also get to read other people's code, use libraries, and so forth. When I open a C file it can be written without care certain times, but C is so self-evident that it's hard to don't understand the code after some superficial reading.
I think C is misleadingly easy: it's easy to reason about, say, a single function body since there's only so many things you can even write in C.
But when you start composing things together and juggling the lifetimes of various things, the complexity balloons and it becomes a lot harder to understand "spooky action at a distance"-type things. You might argue that with good structure and discipline it's still not difficult, but the assumptions are all implicit and you have to get into the mind of whoever wrote it in the first place to understand it. And I write a lot of C and still find it tricky when I have to go look at some random C codebase!
I agree, but what I said has very little to do with that. What you says means that poorly written code has unexpected bugs. But you understand what it does, easily.
> I write a lot of C and still find it tricky when I have to go look at some random C codebase
Absolutely. C Code is not necessarily easy to understand. Not at all.
But the C parts itself are? In C you seldom wonder what C does, you wonder what the C code does.
If you work like OP likes, single person projects, small enough to keep almost completely in your head, I can see how C is especially charming.
You never wrangle with C, only with your own code.
This leads to the same problem as in the C++ world: a fragmentation of the library ecosystem because of wildly different opinions on what "proper C++" actually is.
One example: There's a lot of JSON parsing libraries across the whole spectrum, from using the latest C++ features, or exceptions for error handling, or various subsets of stdlib types, etc... down to essentially being "C with namespaces".
But usually the most controversial features are:
- use of exceptions
- use of RTTI and dynamic_cast
- use of stdlib types which allocate memory under the hood
- ...or even use of any stdlib types
- has boost or similar complex dependencies
- use of 'modern C++' features that explode compile times (like the range stuff)
...etc etc... when you look at C++ libraries written in the game development or embedded hemisphere, those usually use an entirely different C++ subset than the more academic "modern C++" crowd.
The deep-embedded/real-time compatible features I'd agree, that's really a different ecosystem, but that is the case in every language (assuming the language can even be used in such environments). Other than that I don't see it come up very often in practice as something that makes or breaks a library choice.
Rust has explicit subsets of its standard library:
* core, whose only dependencies are six symbols (memcpy, memcmp, memset, strlen, rust_begin_panic, and rust_eh_personality)
* alloc, which layers on top of core, and adds the ability to allocate memory
* std, which includes all of the operating system specific things
But most importantly, you can declare that your code doesn't require std and alloc with an annotation, and there's a way to tag these crates in the package manager as well. Additionally, cargo has a "features" feature, so you can say "please give me the no_std version of the library" and get what fits within your needs.
My team at work is working purely in the 'core' subset, and we're able to easily find and evaluate open source projects that will work for us, because of this.
Oh, I should also mention: while Rust doesn't have exceptions, there's also a way to compile your program so that panics abort instead of unwind the stack, similar to -fno-exceptions being passed when compiling C++ code. There is also a setting you can include so that, if, for some reason, you require the unwinding semantics, if someone tries to build a project with abort semantics and your program, it will not allow it and let you know why. I don't think I've ever seen this annotation on a package in the wild, because since they're not used in the regular course of programming in Rust, virtually every open source library does not use them for important semantics and so therefore works with either setting.
Although `go vet` is nominally a linter it's probably more accurate in relation to other languages to think of it as `-Werror`. Everything it reports should be considered a serious issue, and it runs by default during `go test`. They haven't been moved to the compiler proper out of back-compat concerns (but lately even some of those are being overridden for very pathological things like int/string conversion).
> I don't like Rust: it limits my productivity [...] I try to write safe C code
It's definitely fair to argue that Rust is complex. But this doesn't compute.
You don't like Rust because you're less productive with it so you write... C? Rust is so much more productive compared to C it's not even funny, even with its limitations and extra complexity that are necessary for memory safety. Things that take pages of code in C can be done in a single line of code in Rust.
This starts at fairly fundamental data structures. Need a hash map? In Rust you have one available, out-of-box, with state-of-art performance. In C? Implement one yourself. And good luck getting even a fraction of the performance the Rust one has.
Then there is dependency management and a huge ecosystem of libraries. Do you need to, say, encode an AVIF image? (Something I personally had to do.) Easy. `cargo add ravif`, and two lines of code later you're done.
I can keep on going, but I'm sure you get the idea.
I really honestly don't see how anyone can in good faith argue about productivity and say that C (out of all things!) is more productive than Rust. That Rust is more complex, sure. That you don't like it for aesthetic reasons? Fair enough. But not as productive as C? No way. Perhaps this is where the downvotes you're complaining about come from?
The limits you are referring are not proper of C, but mostly of its standard library. Any long term C programmer has libraries, written by herself or by others, that will limit this problem a lot. An example, starting from your own example. I need a dictionary? I take my rax library, written for Redis. I need dynamic strings? I use SDS (and limit a lot the memory safety issues I could have). C is not well understood because it is seen as the language + its library. But the undefined behaviours and the terrible library are not C original sins, but the terrible work of the ANSI C committee. The Ansi C committee is responsible for a lot of bad things in software in recent years.
But the problem is that C fundamentally limits reusability! I have great respect for you and redis, but your examples are telling: they deal only with strings. With Rust or C++ you can have an extremely efficient, well tested, robust generic map structure that will accept any key or value types. C simply cannot do that, because of the language's limitations, not just its stdlib.
But C can be improved by adding features that allow this and there is a lot of CS experience accumulated around metaprogramming to design this without lots of pitfalls, and no one says that this is easy, this is really hard, but possible. Unfortunately, the committee plays with old and ugly macros system and a little bit with lambda which for sure is better than nothing, but not enough to make C better.
I bet when you say "Rust is more productive than C", what you actually mean is "the Rust stdlib and its wider library ecosystem enabled by Cargo is more productive than what the C stdlib offers", and that is no doubt true. But when you look at just the language, the OPs statement is also no doubt true.
The libraries that make C more productive are out there, they are just harder to find than in the Rust ecosystem.
IME C programmers (including myself) separate a language and its stdlib much more strictly than (for instance) the Rust, C++ or Python crowd, mainly because the C stdlib is pretty much useless for anything that's not a simple command line tool (and because of such restrictions, C programmers also often use a whole language toolbox instead of attempting to write everything in C - Python and C go very well together for instance, right tool for the job etc...).
> I bet when you say "Rust is more productive than C", what you actually mean is "the Rust stdlib and its wider library ecosystem enabled by Cargo is more productive than what the C stdlib offers", and that is no doubt true. But when you look at just the language, the OPs statement is also no doubt true.
No. I also mean that the language itself is more productive, even without the standard library and the tooling. (Although, indeed, to a lesser degree.)
Sum types. Pattern matching. No need to bother with header files. A module system. Proper generics. AST-based hygienic better macros. Procedural macros. Stronger type system. RAII. A proper string type which knows its length. A proper array type which knows its length. Unicode support*. Async + await support. Support for closures. Ergonomic error handling (with the '?' operator). Proper abstraction for iterators.
Do you want me to keep going? Because I can keep going. (:
Again, this is more complex than what you have in C. And it is harder to learn. But that's not what we're talking about. We're talking about pure productivity here, so I'm assuming on one side you have someone who has mastered C, and on the other side you have someone who has mastered Rust. There's just no contest that the Rust person will get things done significantly faster than the C person, because the language itself is so much more powerful. (But of course it's a lot harder to master Rust, so the calculus might be different if you don't want to master your tools.)
* - part of Rust's core so also available in `no_std` contexts, so I don't see it as part of the stdlib but part of the language.
Some language features from your list I agree with (e.g. Rust's "everything is an expression" is really nice), others are just batshit-crazy from my pov (the macro system for instance), others are just more or less random tack-on features (e.g. I don't really see the usefulness of async-await in a systems programming language - especially not when it potentially fragments the library ecosystem).
I would still argue that none of those language features really move the productivity needle a lot into one or the other direction. A good library ecosystem on the other hand does make a difference.
All in all, I would probably add fewer features to C than I would remove from Rust to get close to what I expect from a performance-oriented systems programming language (Zig is actually pretty close to that ideal, but that's a different discussion).
> others are just batshit-crazy from my pov (the macro system for instance)
Ah yes, I won't disagree here; macros get very complex very fast. It's one of the more undercooked parts of Rust.
> e.g. I don't really see the usefulness of async-await in a systems programming language
This really depends on the task, but honestly it is genuinely useful. Even in places you wouldn't expect, e.g. in embedded: https://github.com/embassy-rs/embassy
And AFAIK there are plans to use async Rust in the Linux kernel.
I've heard this often, but I'm not convinced. C just lacks mechanisms to accommodate something like generic data structures, all the libraries I've found feel very hacky, it's either void pointer or macro overload.
> This starts at fairly fundamental data structures. Need a hash map? In Rust you have one available, out-of-box, with state-of-art performance. In C? Implement one yourself. And good luck getting even a fraction of the performance the Rust one has.
I've implemented a hash map in my code. I loved the experience, and I know more about computers and hash maps now that I've done it.
And my hash map was faster than the one in the C++ STL last I checked. I haven't benchmarked it against the Rust one, though.
> I've implemented a hash map in my code. I loved the experience, and I know more about computers and hash maps now that I've done it.
Yep, everyone at least once should implement a hash map. Hash maps are so fundamental and so often used that it really pays off to understand what makes them tick.
> And my hash map was faster than the one in the C++ STL last I checked. I haven't benchmarked it against the Rust one, though.
The one in C++ is very famous for being really slow, so this is a low bar to clear. (:
Not sure if someone directly compared Rust's hash map to C++'s, but Rust's hash map is based on Google's SwissTable, so you can probably assume it performs roughly the same. Here are some graphs: https://youtu.be/ncHmEUmJZf4?t=2093
> The one in C++ is very famous for being really slow, so this is a low bar to clear. (:
There's nothing wrong with the C++ hash-map. It's a different trade-off which makes perfect sense for something that is part of the general-purpose toolbox. See my response above for more details if interested.
> And my hash map was faster than the one in the C++ STL last I checked.
Last time I checked, benchmarking hash-maps, or FWIW any other data structure, in the vacuum did not make much sense to me. Microbenchmarks cannot mimic or predict end user workloads well enough, at least in the cases where it's worth it.
That said, each implementation has its own trade-offs and in particular the one from C++ STL opted in for the pointer stability. For that reason alone, which I think is quite worthy, open-addressing conflict resolution is out of the question because it cannot give such guarantees.
With that in mind, if you're ok that a pointer to your key-value pair can all of the sudden be _invalidated_ by the mere operation of inserting or erasing yet another element from your hash-map, then of course, you can trade robustness for a more cache-friendly conflict resolution algorithm. But then again, this says nothing about the potential performance improvement unless you're able to measure it.
I think your first point on limiting productivity is true only early on (or at least it was in my case) and when working alone.
I pretty confident that this would go away if you write Rust for a few weeks.
I remember when first learning C, I struggled with some things too, but then they just clicked. It's probably similar when learning any new language (especially if they add something novel)
Rust seems nice when you see someone like Jon Gjengset writing it [1], however when actually writing it yourself you discover that your errors have errors which only show some kind of alien fishing when you Google them. Perhaps ChatGPT could help here, it's quite useful/pleasant for trivial quizzes, however I asked it recently how to handle a file upload using actix-web and it hallucinated some really nice methods, too bad they aren't in the code base.
Just a random recent example: call an async function for a side effect without caring what its result is, Rust yells at you that the result must be handled "`#[warn(unused_must_use)]` on by default", so you'd think I would just annotate with `#[allow(unused_must_use)]`, but no way, you must store the result in a _ variable: "let _ = my_async_fn().await;". It's a great language, it's just ugly, sorry, not sorry.
Nitpick: _ is not a variable, it's a wildcard pattern that means "ignore this value". It can occur in other places like tuple deconstruction and the likes.
Nitpicking the nitpick: _ is a wildcard pattern when stated in a match as in [1], otherwise it's indeed a placeholder as a standalone, but also a special character when using it in the normal code flow to allow for unused variables as in `let _foo`. Asked even ChatGPT about it (they must add permalinks for the answers), they answered: "In Rust, the '_' symbol is a special value that can be used in a number of ways. Here are a few common uses: 1. As a placeholder for unused variables. 2. In pattern matching. 3. In iterators."
You do not have to use the result of an async function to avoid getting a warning. You have to use the future returned by the underlying sync function (I.e., you have to invoke “await”), in order for the async code to actually run. That warning probably caught a real bug.
No bug caught, just a mild annoyance. In this setup I have an async function `index` returning `actix_web::HttpResponse` which is to be used for an actix-web route `.route("/", web::get().to(index))`, inside `index` as a side effect I want to call another async function `write_metadata` which returns `Result<bool, anyhow::Error>` but I don't actually care about the result. I can play with the return type of the `write_metadata` async function, making it return a custom error or even a `HttpResponse`, or I can use `let _`, anyhow, lots of work for something this trivial. As said initially: seeing a fully completed Rust project, syntax ugliness aside, it's rather interesting, building one gradually, trying things out, means plenty of pain from meaningless/context irrelevant language quirks.
That’s because Result is must_use. Nothing to do with async.
In that case, yes, let _ is the usual pattern. But it’s rare that I ever actually want to use that. Typically I either return the error upwards or just unwrap. But if you truly don’t care about whether the operation was successful you can indeed ignore the error.
There are plenty of pain points with Rust but I don’t really think this is one.
Btw, the article you linked argued against the claim that Rust’s syntax is ugly, and instead claims that Rust’s noisyness is due to its semantics, largely in service of performance (which is why production C++ code is often similarly ugly).
I also have mixed feelings about Rust. It gives me too many flashbacks from Scala. Complex types, a lot of generics, same elitist attitude. I just think it will end up the same way as Scala. Few years of hype and now we have plenty of unmaintainable codebases from wannabe functional zealots.
I like other communities experimenting new ways. And the Rust lesson will not be forgotten even if Rust will be replaced by something else. However what I do not understand is, given the enormous code base written in C, the fact it will be used more in the future regardless of Rust and other alternatives, is why we don't fix C, at the same time. Create a standard project, within the ANSI C, to write a new, solid, modern standard library for C. Change the language behaviour so that the default compilation target is full of defined behaviours for all the common edge cases, and you can enable an "UB mode" only for speed and if you know what you are doing. And so forth.
This would be really cool. If there would be a more modern version of C, made by C standard committee. Without UB and with small, but robust stdlib. It would have to stay compatible on ABI level, but source level I think we can skip.
For me Zig looks like something that could become a new C. They aim for great C binary and source compatibility (both ways - called and callable from C). It’s planned to be small and lean. And avoid preprocessor and UB as much as possible.
I suspect the problem is as always, political. The standards committee is dominated by compiler vendors, who at this point are desperate to cast C in the dustbin of history, or at least put it in deep freeze, and are eager to push their other favoured languages like C#, C++ and so on. Therefore the committee has decided to please all parties by pleasing none, i.e., keep C stagnated, with only minimal changes that everyone manages to agree on, essentially relegated to embedded work and legacy code maintenance. And as long as C remains under ISO's grip, this won't change, sadly. C needs a rejuvenation and ISO is the last place to make it happen.
Absolutely true. C might have its own way of evolution to Modern C with quite a good standard library and mature features, but people who stay behind C++ don't want this, and even though some of them behave really aggressively if some languages like Rust or Zig or Modern C (unfortunately this is will never ever happened) try to replace C++ or take even a little piece of the pie.
C++ is not a bad language at least because lots of amazing soft already written on it, but some people accept it with all of the myriads of complex rules with their own subset and some people totally disagree with this and just want Better(Modern) C with plain and simple rules without hidden complex things which happened behind the scene, and counterintuitive features.
About Rust is everything cool except sometimes it doesn't allow you to write correct code and push you to do things in ways that you don't like. But we are programmers and some part of us is a painter :)
Both suggestions require companies to invest a lot of their resources (money, time) on something that seemingly is not particularly interesting to them. The former suggestion being somewhat realistic but the latter one I think not very much so. C++ was/is what the companies chose to put their money in to.
I've been writing Rust code since 2013, and did what was probably the first large-scale production deployment of Rust code in 2014, at OpenDNS. Wrote quite a few crate and apps since, and started to heavily advocate for it.
Here's why. In spite of having quite a bit of experience with many functional and imperative languages before, and picking up new languages used to be a seamless experience. But Rust turned out to be an extremely frustrating experience. I was feeling like I couldn't tell the compiler to do what I wanted it to do any more.
I took it as a challenge, didn't want to give up.
And when the first non-HelloWorld app actually got to compile and work (it was probably IPTrap 2, that implements a userland TCP stack), there was this feeling of accomplishment that I couldn't help myself sharing with the rest of the world.
Fast forwarding a couple years... I don't enjoy Rust any more. I still maintain Rust code, but it's a pain, not something I'm having fun doing.
First, memory safety in Rust is overrated. What it actually brings over GC'd languages is that two threads can't share the same pointer at the same time. Nothing else. If I'm not using threads, it doesn't matter. It's not any safer than PHP, Go or JS, but is far more complex. I won't provide memory leaks nor deadlocks either. To some extent, it actually encourages them (I'm often blindly adding Arc, Box, and Mutexes just to silence the borrow checker, not because they are actually needed).
Next, readability is not great. Too many sigils, and the language got more and more complex and less and less readable over time. I'm frequently having a hard time reading my own code after a while. Even more with other people's code. It often feels like people try to maximize the number of language features being used, so that the source code looks impressive, rather than keep the code basic and simple.
Finally, the ecosystem is very unstable. 3rd-party crates (even essential ones such as for error handling, due to limitations of the standard library) are constantly deprecated, abandoned, or having braking API changes. I gave up maintaining some projects, and keep using abandoned (even with security issues, such as old versions of hyper) with others, because the maintenance cost is too high.
After using almost exclusively Rust, choosing Go for a new project (dnscrypt-proxy 2) was enlightening. There were probably runtime bugs that Rust could have detected at compile-time, but overall, productivity was way higher. Making a change is straightforward, I don't have to think about the implications on the borrow checker. The compiler is fast. Performance is fine, and I can spend more time optimizing the actual algorithms than fight the compiler. Go tells you at runtime, not at compile time, if locks have been forgotten. But otherwise, memory safety is comparable to Rust. The runtime gracefully catches out of bound accesses to slices, null pointer derefs, etc.
I also still enjoy writing C code. It's good to have some freedom and control. But C also has rough edges. UBs, portability, minimal standard library.
Zig fixes many issues that C had, and is the language I really enjoy the most now. It's fast, portable, easy to learn, easy to read, has proper error handling, strong typing, catches out of bound accesses to slices and does checked arithmetic, and, overall, is a joy to use. Not to mention its seamless integration with C.
It doesn't have temporal memory safety, but, like Go, Zig's simplicity helps me reduce logic bugs, and focus more on algorithms than their implementation, overall resulting in better code.
Thanks for sharing your perspective. I think, broadly speaking, that code maintenance is no fun. Thus, languages with a huge existing code base are often dreaded (Java, C++).
Rust profits from "rewrite it in Rust", since it is more fun. But so does Zig.
Still, I am worried about the ecosystem, since every time I pull a bigger crate through cargo, I have to think of npm and the sad state of JS ecosystem. Also, I have a small, rewrite from python project at work and I am also worried whether it will be easy to upgrade in 2, 3, 5 years? If I write it in Java, it certainly will...
Fully agree on readability. It is not that Rust is unreadable, but a lot can happen in the background and you have to look hard for small sigils, presence of semicolons and blocks.
I think that Rust is great option for writing new software where having any kind of automatic memory management isn't an option (or even if possible is a lost battle trying to convince others), like kernel code, drivers, type 1 hypervisors.
Otherwise there is Go, Haskell, OCaml, Java, Kotlin, Scala, Clojure, Lisp, Scheme, F#,...
If you don't like Rust, try a Lisp. You're probably looking for one that can compile fast code and has good, easy to use C FFI support. Gambit, Chicken, SBCL, CCL, and ECL are all good choices here, with Guile 3.0 also potentially a contender.
If Redis works then the proof is in the pudding, I guess. But as an average/below average programmer I’ll stick to something more modern when I need that level of control.
> 1. In order to reach memory safety it add abstractions that don't make programming simpler, but harder.
Another possible conclusion is that your program design is just not as simple as you thought it was, and Rust is pointing out why. This drives you to make it even simpler, which is arguably good.
I understand why the OP likes C. I was looking at a couple of old projects of mine and I remember the satisfaction at least in one (a SASL library) of how I made various classes of error impossible using a fairly simple strategy and my own string handling routines. In a memory safe language with lots of assistance for threading there would never have been that pleasure. It was not amazing or anything but it was elegant to my eyes.
I feel I have to develop an economy of effort in C because there usually isn't a dumb and easy way to do things - this pleases me.
C has a mental model that seems relatively simple - I feel I can reason about what's going to happen to a greater degree than higher level languages.
C++ is the worst IMO because it has lots of abstractions but they don't make programming simpler because you pretty well have to understand the low level aspect of what they're doing to get performance - so the complexity is much greater.
Rust seems to put a lot of difficulty up front which is of no value in the kind of typical short program I write most of the time. I just didn't enjoy it. If I had something complicated to do I might eventually appreciate it.
The more high-level the language the less one tends to understand it and that's unsatisfying.
Having said all of that though, I tend to "just write python" whenever I can. If it's a throwaway program one doesn't need more and if it turns out to be necessary to interface to some library or other I know C well enough to sort that kind of thing out. I use threading.....never - at least never by choice.
I have found pleasure in combining „just write python“ with some C compiled code linked in and bridged via numpy for performance critical loop. I just love how fast I can write code in Python for when I do side projects
Pybind11 has significant overhead that makes it non-viable for some projects, even if it has a nice API. For pysimdjson we switched back to cython for an order of magnitude improvement.
Its magic function wrapping comes at a cost, trading ease of use for runtime performance. When you have a single C++ function to call that will run for a "long" time, pybind all the way. But pysimdjson tends to call a single function very quickly, and the overhead of a single function call is orders of magnitude slower than with cython when being explit with types and signatures. Wrap a class in pybind11 and cython and compare the stack trace between the two, and the difference is startling.
Ah yeah that makes sense. I would rather call a single C++ function from Python that calls other C++ functions (or itself). In case of pysimdjson however, Cython makes much more sense.
Overall this is way better than writing everything in Rust.
I'm pretty much in the same boat and I don't even like Python the language. The ecosystem and extensibility is just so insanely productive it even rivals Excel...
In re Rust: I feel one of the mistakes most people make is taking Rust to be a general purpose "default language" for everything, like C and C++ used to be. But like you wouldn't write C++ for most stuff even if you were magically granted the ability to never ever write unsafe code, writing web services in Rust just means making your own life harder. It's a great tool, I'm happy it exists and I follow its development, but we must accept it exists in a different space than, say, Python or Ruby or Javascript.
I feel physical pain as I say this, but most stuff should probably just be Java.
> I feel physical pain as I say this, but most stuff should probably just be Java.
This has been on my mind a lot lately, causing similar anguish.
I'd note that, from what I hear, C# would also fit the bill, but be as unappealing to me.
I really wish that there was a language with the productivity, ecosystem and ergonomics of Python, with the speed and efficiency of Java and C# and a type system that increases productivity rather than word count.
Go seems... fine, but, I dunno, slightly regressive. I'll probably use it where performance and scaleability matter.
> I really wish that there was a language with the ~productivity~, ecosystem and ~ergonomics~ of Python, with the speed and efficiency of Java
That would be.. Java :D While I’m quite a big Java fan, for the other parts some other JVM language can easily fit the bill and you even get a choice. Scala, Kotlin, or maybe even Clojure if you don’t mind dynamic types.
Yeah maybe, I hear Java has improved markedly since my bad experiences with it (admittedly a long time ago now). I should swallow my distaste and give it a fair trial. From a distance it still looks somewhat verbose and unergonomic.
The others look like really really nice languages and I'd love to use Scala for a solo project.
However I guess I need something that's a) truly general purpose b) not going to scare away collaborators or potential hires c) going to be around for a long time d) not going to require evangilization at, say, a start-up.
Kotlin... I guess I'm wary of it being the new Coffescript - i.e. that it spurs Java into improving to such a degree that it removes the need for Kotlin in the first place.
And yes, having just spent a long time transitioning a project from JS to TS, I'm not sure I want to start anything serious without at least half-decent static typing - a major reason I'm looking for an alternative to Python, which is just not quite there.
For what it worths, Scala 3 is a really great language, it now even has a compiler option to exclude nulls, making you use something like `String | Null` to denote nullable values.
Yeah, from a purely personal point of view Scala 3, F# and, maybe, Elixir seem to offer the greatest promise of productivity, safety and expressiveness.
> Kotlin... I guess I'm wary of it being the new Coffescript - i.e. that it spurs Java into improving to such a degree that it removes the need for Kotlin in the first place.
I think that's unlikely. The key difference being that Coffeescript was always dependent on JavaScript because it compiled to JavaScript! Whereas Kotlin doesn't compile to Java, it compiles to JVM bytecode the same as Java does.
Java's gotten a lot better and maybe some day in the future I'll go back to general purpose java for server-side development, but there's enough velocity increase in Kotlin that I feel more and more productive. The real victory of the language IMHO is absolute null awareness (at least for largely Kotlin codebases). This makes any sort of nullability issue perfectly clear as long as you're not '!!' (null-safe opt out) everywhere like a lunatic (or as a newb when I started). Being able to seamlessly weave java and Kotlin in the same projects has made the slow iterative migration a lot simpler as well. All the same dev toolchains/ecosystems, just a different compiler plugin.
I really like Kotlin on the backend, it integrates nicely with Spring Boot, etc., but as soon as I say I have Kotlin experience, people say "Oh, so you're an android developer?"
I've never touched android development in my entire life.
Kotlin has its future assured thanks to godfather Google, stagnating Android Java on purpose (only recently they decided to finally move to Java 11 LTS subset), while promoting Koltin as its replacement for everything besides core platform.
I've been really enjoying Dart, I can write code slightly faster than in Python because of the type system and deep IDE support.
Unfortunately, when I use Dart, it's on Android, where the SAF exists, which kills my enjoyment, despite Android/Flutter otherwise being probably my favorite platform.
> I really wish that there was a language with the productivity, ecosystem and ergonomics of Python, with the speed and efficiency of Java and C# and a type system that increases productivity rather than word count.
>I really wish that there was a language with the productivity, ecosystem and ergonomics of Python, with the speed and efficiency of Java and C# and a type system that increases productivity rather than word count.
Nim has that except for the ecosystem. And it's also kind of janky, enough that I got so frustrated I stopped trying to use it. But you might have better luck than I did; check it out if you haven't.
Yeah I know lol, just trying to be honest. It's a genuinely fun language to work with; if it wasn't I wouldn't have put in so much time trying to make it work with the way I like to program. I still recommend you check it out, because people like to code in different ways, and it might fit your style of programming more than mine.
Iterators claim to be first-class but aren't. And "zero-cost" iterators that are only zero-cost if you don't value your own time spent debugging them. I've been able to crash the compiler when trying to write what in Python is idiomatic "itertools-y" code. And whenever I complain about this, someone pops up to recommend some third party iteration library. Which is part of the problem; I shouldn't need a third party library full of terrifying-looking macros just to write lazy iterators. They don't live up to what the docs promise, which is a real shame.
If they made iterators work how they seem they should work, I would come right back to Nim. It does so many things right but this one thing is a dealbreaker for me. The UFCS is practically begging me to chain lazy iterators together but actually trying it is like stepping on a rake.
What have you switched to from Nim? Have you gone back to Python and put up with the speed penalty or chosen something else?
Lazy iterators and generators are something I've missed in Rust. Haskell has them just by nature of everything being lazy and I love that. But I've become too familiar with Python, and unless runtime performance is a bottleneck, Haskell hasn't been the path of least resistance for most of my practical problems.
> What have you switched to from Nim? Have you gone back to Python and put up with the speed penalty or chosen something else?
Just Python and shell scripting. All this stuff is for hobbyist programming not my day job, so I just shelved the stuff that need performance and dusted off some projects that don't. I'm learning Rust at the minute but it's slower going, I'm not at the level where I can make the stuff I want.
As someone who equally loves C for all the reasons outlined here - I never understand the Java hate. Its an extremely practical, pragmatic language. Not every program has to be a technological marvel. Most code exists to get stuff done, and having code that gets stuff done and ISNT super complex is a pretty good thing, IMO.
I don't hate Java, I work with it every day and as you say it's a practical language that gets stuff done with minimal hassle.
The reason why it's hard to stomach is that it's the infrastructure of the future with the programming techniques of the past. Java the environment is like living in 2050, Java the language is like living in 1990.
Things are slowly getting better, but everything feels clunky and bureaucratic. The type system manages to be simultaneously ultra-verbose and not expressive enough to represent useful properties. It's a complete travesty that I need to codegen my own Tuples and write my own Either<T1, T2> as a literal Bohm-Berarducci construction. No value types (they're coming, but not yet stable). Didn't have Records until Java 18. Useless semantics for "final". Getters and setters. Nulls aren't even funny.
The language design feels at odds with the broader goal of providing a safe, efficient, solid programming environment.
It's still a fine choice for most applications, I'd venture to say it's actually the best choice, but that's because the language itself actually matters very little, unlike us nerds would like to think. But it doesn't have to be that way. I'm hopeful it'll get better.
> a lot of difficulty up front which is of no value in the kind of typical short program I write most of the time
Wonder what are the examples of 'short programs' where you'd just zip out raw C as a default language, and it would be simpler/faster to implement than in Rust?
The comparison is really to python rather than C. I C mostly when I'm trying to fix some compilation bug in a library or when I'm trying to call some glibc or other OS level bit of code to achieve something- e.g. on a Raspberry Pico.
Using C for anything is just rare for me and C++ and Rust even more so because they don't add anything that I need usually.
I've found myself thinking, time and again, is Rust exposing, to the programmer, what have traditionally been details handled to varying degrees by sophisticated compiler optimizations? If so, a great optimizing compiler might deliver binaries as performant as a Rust expert could, but with less programmer effort?
(I can't answer that, only haven't found a counterexample as a one time optimizing parallelizing compiler person.)
In my experience, the details Rust is exposing are less about performance and more about correctness. For example, Rust forces you to be explicit about:
There are some performance gains to be had in that list, but for the most part it's about asking the programmer to be explicit and avoiding convenient-but-risky defaults that lead to lots of bugs.
I'm currently making a language that translates directly to C.
There won't be pointers, and I think everything will be passed by value.
I'm not sure that compilers can optimize this and "move" returned data. But the goal is to greatly limit side effects and improve cache locality by encouraging the coder to use more functional habits instead of oop.
There are grey areas because I'm not sure the design is sound, but even if it's not, it can be safe and fast enough.
I also like to program in C; the fundamental error is not programming in C (or liking C), but thinking that you’re special or uniquely able to use C safely.
The author adequately defends their predilection for C, which was never doubted; they don’t produce a great argument for why C is good (which, perhaps, they didn’t intend to). Most of the defensive programming techniques covered in the post reflect this: asserts, for example, only catch the bugs you know about.
Exactly. Objectively, there's no such thing as a disciplined and effective C programmer. Everybody makes mistakes. You can't guarantee that you won't. Believing that you can is in itself dangerous. It's delusional. Experienced programmers know they make mistakes and even anticipate that they will. My attitude these days is "I wonder where I messed up; let's find out!". Basically, like a good scientist, I try to falsify the hypothesis that I messed things up. Only when I start failing to do so, I move on. I make all sorts of silly mistakes. Off by 1 errors, logic errors, inverted conditions, etc. You name it, I do it.
There are all sorts of valid arguments for not immediately porting over the huge legacy of C/C++ code bases to something less likely to blow up in your face. However, the amount of valid arguments for starting new code bases in those languages is rapidly declining. I'm sure there are some corners where you just have no other valid choice and that's fine. But increasingly it's just a matter of people being stubborn, rigid, and unwilling. Half the success is just understanding your own limitations and mitigating those. The rest is just dealing with the inevitability of people messing up.
Controversial opinion, somewhat philosophical: I use C because I still believe in freedom, and I think you should too.
If we gradually destroy all semblance of individual thought and discretion and replace that with something that essentially approaches rule-by-machine, what remains is not far from an authoritarian dystopia in societal terms. Even more so when you realise the direction Big Tech is taking. Jailbreaking and rooting are an escape hatch that they're certainly trying to close with all the "memory safety" stuff.
C also has an advantage of being simple enough that a single person can write a compiler for a nearly full subset of it. The same can't be said of many other languages.
I'm not saying I can write 100% correct code, although I certainly try (and IMHO it's not too difficult to get close.)
"Freedom is not worth having if it does not include the freedom to make mistakes."
Is there no semblance of individual thought in C++? No discretion in Python? Are you ruled-by-machine if you use Go? Is using Javascript an authoritarian dystopia?
Apparently the road to hell is paved with vague slogans...
I don't agree with the grandparent post but can offer an interpretation of what they mean. Languages like Go, Rust, Java, etc. are enormous and are designed and managed by a committee. Sometimes, they go in a direction that you might not like. For example, many people were pissed off by the compatibility break between Python 2 and 3. If you find yourself disagreeing with the designers' decisions, you have very little recourse but to stick with an old version of the language/tooling while the rest of the world moves forward, because you are in no position to understand and maintain a huge language implementation codebase. The grandparent is implying that C is so simple that if you disagree with the direction of the official C language and major compiler vendors, you can simply write fork the language and reasonably your own compiler. But of course, I would say that you have the freedom to reinvent the same bugs over and over again, that the committee was trying to proactively solve for you.
I think equating a compiler that stops you from making runtime mistakes to authoritarian social dystopia is ungrounded, baseless and frankly dumb. My idea of freedom is not wasting my life debugging null ref errors on weekends because somebody else wants the freedom to do bad ideas.
to me the assertion has merit from the point of view that being able to understand what your application does completely is much easier in a small language like C or lisp/scheme, which i believe is essential to maintaining the proper master -> tool relationship with the computer. it seems straigh forward to me that if you use things you dont understand it is likely you will open your application to all sorts of potential unexpected behaviour
Having worked on C and C++ compilers for a few years at this point: I would say that there’s a strong negative correlation between claiming that C is a simple language and actually understanding C, as specified.
C is actually a fantastically complicated language, both syntactically and at the semantic and abstract machine level.
while i said its small, not simple, i do not think it is controversial to say that c is simplER than rust if what you aim for is a complete understanding of your aplication
I think that is controversial: Rust has more complex syntax than C does, but has a substantially simpler semantic model. Just a small example: given just `T*`, neither you nor your compiler knows what your global aliasing set is. The compiler has to do significantly more work to prove exclusive ownership (necessary for many optimizations), and you have no formal guarantee that your data doesn’t change underneath you.
They’re comparable, as evidenced by both being representable within existing compiler IRs. But no, I said that C’s semantics are significantly more complex.
my view of c is as an abstraction over assembly. from this perspective i just cannot see that rust provides simpler semantics, unless you are trying to make c do something which you should probably use another language for, like lisp :)
This is what I mean: C is absolutely not a naive abstraction over assembly. The C standard is clear on this: C is defined in terms of an abstract machine, which in turn doesn’t cleanly map to any particular machine architecture.
Treating C like a “high level assembler” needs a rule like the cryptographic doom principle: it will inevitably lead you astray, and “astray” for C generally means memory unsafety.
> Treating C like a “high level assembler” needs a rule like the cryptographic doom principle: it will inevitably lead you astray, and “astray” for C generally means memory unsafety
you are right. however, i think c does a better job at being a model of an abstract machine than rust. if we restrict semantics to that, and the developer accepts that they need more knowledge than just c, i think the semantics are simpler than rust's. in the same way, dont you think that rust can create a false sense of security to those not completely understanding what protections it provides? if we see programming languages as a hci problem, then i think c by default offers more freedom, and consequently more unsafety. amyway i dont want to come off as hating rust, i think it is a good language, but more importantly its community seems fantastic and i wish it success
> dont you think that rust can create a false sense of security to those not completely understanding what protections it provides?
No: the entire point of Rust's design is that you don't need to know the abstract machine semantics to write Rust safely. The compiler enforces the semantics for you, rather than pushing them onto you.
C on its own does feel like a simpler language though I think it’s true that the semantics taken literally undermine it (the generated assembly can be really surprising sometimes). I do trust a given Rust program to have a specific runtime behavior much more when optimized. It might be interesting to come up with a variant of C that has genuinely simpler semantics as well (not sure how useful I’d find it though)
It’s a common argument that a more simple language makes for more understandable code, yet practical programming tells a different story. From web browsers to every major C compiler, C++ is the language of choice. Similarly for GUI frameworks, GPGPU programming, etc. So what is it? Are we supposed to believe the, frankly pompous, notion that those developers/firms/institutions/etc were tricked? No, I think not.
If we are being honest with ourselves the truth is that even a language as nightmarishly complex as C++ is preferable to C. And whether or not you agree with that the fact is that the world marches on regardless.
I feel like this is trying to solve an ethics issue using technology. It would seem more appropriate to fight for freedom by joining or supporting the people who already fight for it, than sitting and thinking by using C one helps the world in that regard.
I’ve seen you post a variant of this comment on multiple Rust-adjacent threads, and I still don’t understand it: Rust was by no means the first “safe” language, it wasn’t the one pushed by Big Tech (that’s Go, another good language!), and there’s no particular evidence that preventing jailbreaks is the actual, secret intention here. That last part borders on conspiracy, given that engineers are overwhelmingly anti-DRM and pro-modification.
given that engineers are overwhelmingly anti-DRM and pro-modification.
If they were, things wouldn't be the way they are today. Perhaps you're saying that they are, but can only express that position in the form of subtle exploits (which seem to be decreasing over time)? No, I think the majority of engineers are overwhelmingly complacent and obedient, and won't oppose.
As for why Rust, that's because the amount of prosletysing of that language seems to be far more than all the others.
Dont mind the downvotes, I somewhat agree with you with the the lack of freedom argument (although not quite sure about the larger context around dystopia).
To put it in other words, I want my tools to do whatever I want them to do and not the other way around.
I want my tools to do whatever I meant to write. If I made what I consider a mistake, I want a tool to point it out. And I want memory safety, because without that, terrible things can happen that nobody ever wrote.
pointing mistakes is the job of the linter, the job of the compiler is to run my program asap and even try to fix my mistakes automatically. There is nothing more pedantic than a compiler saying "Oh you shouldnt do this as it can potentially be a race condition"
Rust is a defined space. It doesn't restrict your freedom of thought or speech on your personal GitHub, you can use C or brainfuck there just fine.
Rust just says "This is an engineering project, not a philosophy paper, or science lab. It is not the time or place for experimentation, new ideas, and individual discretion, or for trusting each other or ourselves, we want reliability at all costs".
Which is exactly the attitude I want to see, when failure means I lose important notes, money, or get lost because GPS crashed, or my battery runs down because everyone was too afraid to optimize their brittle bug heaps, or a self driving car murders me, or we return to 1970s level tech due to nobody trusting software.
You can absolutely code in Rust for fun or experimentation. There's absolutely room for new ideas and individual expression in Rust. It's not a straight jacket. It's like you and the compiler are pair programming together; it's a collaborator, not a task master.
> Rust just says "This is an engineering project, not a philosophy paper, or science lab. It is not the time or place for experimentation, new ideas, and individual discretion, or for trusting each other or ourselves, we want reliability at all costs".
I'm rather fascinated that you put this in these terms, because I've seen the exact opposite (specifically the philosophy paper). The few times I've seen rust tried out, it seemed to turn into a competition between devs about how clever they could be with category theory. Then an abstraction stack grows until logic errors creep in because nobody understands what the thing is actually doing anymore.
I don't want to blame rust specifically for this, because the same thing happened with scala. Not sure what it is, possibly just a lack of dev discipline. I can't help but view this category of languages (rust, scala, haskell, etc) as competitive philosophy because I have to spend several hours sitting around pondering the nature of things before I can make minor changes from my jira ticket.
End result ends up being that someone usually re-writes an "interim" solution in java/go/etc, over a couple of sprints and then nobody ever gets back to the long term solution written in the elegant language because they don't have time to understand it.
As a counterpoint, I wrote an "interim solution" in Rust in a few weeks that ended up replacing both the python and C++ versions that had been months in the making. I suspect the difference might be more about the skill and pragmatism of the developers involved than the languages used. Heavily functional code is not consisdered idiomatic in Rust.
They're all plenty skilled, but I'll give you the pragmatism one. A certain style of dev seems to have a compulsion to violate Kernighan's Law.
Eventually that abstraction hierarchy collapses on itself. I don't want to pin this purely on rust either, since I've seen it in plenty of languages (including java), but I see it happen often enough that I'm reluctant to trust my co-workers with rust at this point.
Or maybe they all can and I'll retreat into my little abstraction-minimal kingdom where the code's not very elegant, but I can figure it out, and I can onboard people in less than a month.
I laughed a bit when I saw “memory safety” and C in the same sentence. Your program is only as good as the programmer writing the program. Having said that, it just takes one missed check to cause a buffer overflow in C.
The whole bulleted list from "Custom Memory Safety"... this makes me want to run away towards the horizon and scream.
In the 90's it was very common that big shops that did some programming would have their own languages, which continued into the 00's in the form of everyone rolling their own C++ standard library. I had the "pleasure" to work with both. Invariably, this stuff was so much worse than off-the-shelf stuff and yet the authors believed themselves to be geniuses.
I mean, there's a chance this guy did something outstanding and actually improved something about C, but so far I've seen so many fail to do that in such a predictable patterned way I just don't want to see any more of that.
My favorite C code is the one that has no macros, as few of typedefs as possible, no acrobatics with alloca() or any platform-dependent stuff. I think, FIO is a good example of what I'm talking about.
The whole thing just sounds like "yeah, it's a swamp, but it's my swamp and I like my swamp!" kind of thing.
Well put. Another way to think about it: every major OS and systems vendor has had some custom “safe” C variant over the years, to empirically mediocre results. It's unlikely that this author has done better; it’s much more likely they simply haven’t received the same scrutiny.
> (...) big shops that did some programming would have their own languages, which continued into the 00's in the form of everyone rolling their own C++ standard library.
It sounds like you're talking about using libraries as if it was a bad thing.
> had the "pleasure" to work with both. Invariably, this stuff was so much worse than off-the-shelf stuff (...)
Where do you think "off-the-shelf" stuff comes from? It sounds like you're trying to mistepresent the adoption of subjectively sub-optimal libraries as somehow the issue caused by using libraries and frameworks.
> mean, there's a chance this guy did something outstanding and actually improved something about C, but so far I've seen so many fail to do that in such a predictable patterned way I just don't want to see any more of that.
You're showing a tremendously naive and misguided belief that libraries are somehow bad.
Languages such as Rust flourish with the adoption of third party libraries,some of them euphemistically described as "not stable". Languages such as C++ flourish with third-party components like Boost and POCO. But applying the same principle to a language like C warrants an automatic and mindless putdown like blindly accusing anything of being bad? It makes no sense.
A more generous interpretation would be that if you have a communal library, rather than everyone having their own individual libraries, it's easier to vet it for correctness. Rust takes the communal approach. The parent was suggesting that every company has an insular library approach. NIH.
Many people that go off the beaten track often fall into the same ditch.
Correctness yes but what about other requirements people have such as performance, extended functionality, domain-specific optimizations, predictability, etc.
Third-party libraries were not born out of the thin air but because of different parties having different and very much often disjoint requirements so it's not particularly NIH syndrome.
There is a huge gap between something like boost that is designed to be reused and internal libraries that are subject to the usual corporate constraints (the least amount of work is done if even that).
> There is a huge gap between something like boost that is designed to be reused and internal libraries that are subject to the usual corporate constraints (the least amount of work is done if even that)
You're somehow trying to imply that libraries are not reusable if they are not widely shared, which makes no sense at all.
The best argument you could make is regarding stability, but you simply cannot lay any such claim just from the library's licensing.
Accusing internal libraries of being half-baked, badly designed, or bug-riddled is just a cheap blanket putdown.
> It sounds like you're talking about using libraries as if it was a bad thing.
I don't see the relationship... how do you make such a leap?
> Where do you think "off-the-shelf" stuff comes from?
I worked on proprietary projects that eventually went public domain. So, I can tell you where off-the-shelf comes from from personal experience. Proprietary in-house tools have the benefit of not having to deal with many things that an off-the-shelf tool would have to deal because they can choose the kind of hardware to run on, the kind of supporting libraries, their versions and combinations to use, the kind of developer tools to support and so on. off-the-shelf tools, in order to be successful must cover a much wider area of ecosystem in order to be relevant.
Here to give you a better example: C doesn't have its own concurrency primitives, no concurrency model etc. But, you could build an extension, let's call it CC to have some sort of concurrency based on pthreads library. If, for your internal needs, you only use CC on Linux -- you are golden and everything works fine. Once you try to make CC into off-the-shelf product you have to do something about pthreads missing from other platforms.
Off-the-shelf products are typically born as in-house products, but they need an extra step to become what their name implies.
In the specific case of what OP described as their own extension to C, I can vividly imagine a language that doesn't cut it as an off-the-shelf one. Making an off-the-shelf language would've also guided OP to a much larger departure from C because memory safety is only one of the big problems with the language. And this is what happened to a bunch of other languages which already walked a big chunk of that departure journey. This is why I would've been frustrated having to use the language like OP's: it would've felt like not enough change and too much headache to adjust to my target environment than could be had by using actual off-the-shelf languages.
> You're showing a tremendously naive and misguided belief that libraries are somehow bad.
I have no idea where you get this from. If anything, I say the exact opposite...
> My favorite C code is the one that has no macros, as few of typedefs as possible, no acrobatics with alloca() or any platform-dependent stuff.
You've just outlined a perfectly good subset of C. I'm pretty sure people who write C extensively even to this date mostly end up there. The beauty of using C these days is that one gets to control every single byte without going through hoops and loops. Also, any C code can be reliably integrated into other languages using FFI, which is quite universal. So, yeah, no need to create dirty hacks.
> The beauty of using C these days is that one gets to control every single byte
No, not even close: Struct padding, compiling switch as if-else vs. jump, implicit arithmetic promotion to int, stack layout of local variables, and many other issues.
If you want to control every byte, write in assembly language.
Nit: struct padding and arithmetic promotion don't support this point. In practice, C compilers provide struct-packing extensions that cover the uncommon cases where default padding wouldn't yield the layout you want. And they'll let you opt-in to a style where arithmetic promotions are forced to be explicit as they are in e.g. Go. So it would usually be ridiculous to drop down to assembly for either of these reasons today.
C++ is the F-35. Much touted. Expensive to maintain. Expensive to get rid of. Doesn't quite live up to the hype. Borland C++ and Visual C++: never the two shall meet. They might as well been entirely different languages.
Given all software problems that plagued F-35 during all its development, including mid-flight avionics reboots, I don't think Bjarne Stroustroup actually realizes that it isn't a good idea to advertise it.
>This means that I get many of the advantages of Rust
Arguably, you don't. The big difference is whether your compiler enforces and tracks your documentation, assumptions used by your code, often critically important for correctness. Unfortunately, Rust is far from being perfect in this regard (e.g. SPARK probably does a better job here), but it significantly improves on C/C++ in this regard.
>I Work Alone
Unfortunately, even if you are the only soul touching the code, it often quickly becomes false. On a big enough project, "alone" inevitably becomes "me and that guy who wrote this part of the code". This also can be formulated as "me and 2-years-ago-me". In other words, we quickly forget a lot of context used for writing a piece of code.
>Rust is too big for my small brain, unfortunately.
I think the lack of hubris, the understanding that small human brains are not enough to keep track of all necessary context required for writing correct software is the key element for liking Rust and similar languages. Rust is not about building ivory towers of abstractions for the sake of it (though some people try to walk in this direction), but about offloading as much as possible of the context on compiler. Unlike humans, the compiler does not get tired, it does not forget. Yes, it's limited and because of those limitations it requires to restructure our code and sprinkle it with additional information, but more often than note it's a good thing.
As sel4 and other formally verified software demonstrates, it absolutely can be done in C. But one could say that C used in sel4 is not a C language anymore. After all, you can not take a random C library and use it in sel4.
There are 2 things I've obsessed about in C & C++ land:
1) Why are we explicitly directing a loop forward or backward most of the time when it's not necessary. The compiler should direct the loop unless we want to explicitly do this for the body of the loop. My thinking is that there may be performance reasons to reverse a loop or even split it into separate threads. (unlikely)
2) In C++, I've always wondered how template types get instantiated for POD that is represented the same at the bit level. Does the compiler duplicate the templated code or is it smart enough to say "this std::vector<int> is the same as that std::vector<intalias>". Rust prides itself on zero-cost abstractions, I have wondered if they reduce binary bloat in this area. (as a naive C++ person)
1) The compiler is already allowed to reorder loop evaluation if it can prove that it can't have any observable side effects. If it can't, there could be subtle dependencies on the order of evaluation that the person writing the code and tests wasn't aware of, so you're just asking for subtle bugs that are missed by tests and could even turn into vulnerabilities in production.
2) It duplicates, and the standard even comes with asinine "guarantees" that actively sabotage deduplication, such as requiring all functions to have different addresses. Which means the compiler can reuse the same function, but only one function label can start right at the code and the others need to NOP-slide or JMP into it. Which unduly pessimizes precisely those functions that are most likely candidates for deduplication: Those that themselves only consist of 2-3 ALU instructions.
Nr (2) is not correct. Compilers have been inlining functions for decades so the argument of "all functions to have different addresses" does not hold. Compiler can even _merge_ two different but similarly looking functions into a single one. And that's just part of the code transformations compilers can do.
And this goes without even mentioning LTO, which has been explicitly designed for optimizing the code layout and size during the linking phase.
So, code deduplication is a real thing and happens regularly with your code. Templates are no much different than the non-templated code written for the same purpose.
If you take the address of different functions, the standard requires those to compare unequal.
However if you merely call functions and never take their address, the standard doesn't put any requirements on that, so inlining etc. is legal.
But without LTO, it's impossible for the compiler to know whether a non-inline function with external linkage will be called or have its address taken, so the compiler must ensure that it has a unique address.
There are linker level optimizations aside outside of LTO that can merge functions. gold's --icf option comes to mind, with --icf=safe meant to be conforming.
All that sounds ... reasonable? What I didn't agree with is the "requiring all functions to have different addresses" argument. It's invalidated both by the compiler (e.g. inlining) and linker (e.g. code folding).
If it can prove that nothing can observe the address of the function(s). Inlining renders the whole discussion moot.
The point stands. Compilers cannot merge equivalent functions in cases when it makes sense to even think about this optimization, which is when it actually has to export an externally visible symbol for the function.
And that's what happens in probably like 99% of the cases. I objdump the code quite often to understand what happens under the hood and rarely I see that the similar code has been duplicated. There could be of course examples but I just didn't agree with "standard ... actively sabotage deduplication" sentiment which to me reads as something universal and not an exception.
I think active sabotage is a correct assessment when a simple, obvious optimization is explicitly prohibited for no (defensible) reason and can only be applied by extensive whole-program analysis that allows the nonsensical rule to be bypassed completely. It's still sabotage, it's just mitigated by the extremely smart compilers we have nowadays that basically pick the program apart and rewrite a functionally equivalent one.
1 isn't really the same thing. I don't know any compiler that would turn
for (i = 0; i < length; i++)
into
for (i = length; i != 0; i--)
Which is what I read 1 to be.
As for why you sometimes do one over the other. I think it is true that computers branch on zero more easily than other values. That said, I'd be a little surprised to know it makes a difference that can be measured in any meaningful program.
See this is why I felt I was obsessing over something very small.
Loop inversion, unrolling, and splitting. The compiler can do so much with a loop to make it run faster on 'this machine' to optimize for speed, memory use, or binary size.
In regards to #1, I mean we shouldn't be asked to explicitly direct the iteration of the loop. We should say "loop over these things" rather than "start from 1 and stop at 10". MOST of the time we're explicitly stating these things - that the compiler can undo - so what's the point? If the compiler is doing whatever it likes, why are we expending thought on which direction a loop should iterate?
I love that Rust has eliminated the egoism around "you should know at all times when something is allocated or deallocated". We're trusting the compiler to do that better with zero cost to us. So take it to the extreme: Only specify the loop direction if you have a true requirement to do so.
This is my Ted Talk, thank you.
For #2 - LOL! I had no idea it was essentially aliasing those functions to get around a requirement for uniqueness.
> When it comes to the enjoyment of programming, I hate all of them. With a passion.
> Except for C.
I get the sense that this is the real reason, and the rest of it is justification that they understand the risks involved but choose to do it anyways. And honestly, I think this it's fine? I'd argue that it's a little inconsistent with their statement above that avoiding C/C++ in lieu of memory safe languages and should be followed by default, but they subsequently admit to being hypocritical about this, so I'm not really concerned that they don't understand the benefits of memory safety. When it comes down to it, I have middling confidence that I would be able convince someone who still at this point doubts that Rust is safer than C, but I have absolutely no illusions that I can convince someone to change a personal preference. If anything, it's a bit refreshing to see it freely admitted, given how often it seems to be the real reason for a lot of choices of what language to use (and I don't just mean this for C/C++, but for any language, including Rust).
I think he's fairly transparent that it is the only reason. "I program in C because I enjoy it even though I know objectively it is not a terribly good choice for building software" is pretty much the entire article.
If you need C like memory access (embedded, performance..) I highly suggest zig, which is maturing really fast.
We use rust and zig in house for our development and while rust is preferred for complex projects, we prefer zig when we need: super portable code, code that will work with a static memory buffer or datastructre that are hard to use in rust (like self referencing).
We still have some chips that requires C because they have proprietary compiler, for those, we are very eager for the zig->C compiler that should be coming some day.
I'd argue that compile-time is really nice exactly because it can be mixed with runtime. It takes some time to get a good grip of it, but in the end I realized that a lot of code can actually be made comptime.
I agree that async is peculiar, but I think it is still too early to decide if this works or not.
Compile-time evaluation is easier if you think of it as a separate language for pre-computing and generating zig code where most of the time it's used to supply the type of a parameter or construct a container like you would with a template.
You have copy-paste operators like `inline for`, `inline while`, and `switch (x) { inline else => {} }` that focus on generating code given a value known at compile-time (think of it like running a C program to print out the code within a `for` loop then inlining that in your code) and are great when you need to unroll a loop or loop over the fields of a struct (e.g when printing it). If you find those difficult then try printing the code they inline to get a feel for how it changes your program or writing what they'd inline manually.
For the more template style `ArrayList(T)` style you can follow the logic yourself by writing the struct it would result in when you substitute the parameters (again a copy paste but with slightly more going on) and any more calls with the same parameters will always return the same struct back.
Branching at compile-time is like having a smarter `#ifdef` for the most part where you use `if`, `switch`, and other control-flow to decide which code should remain in the program as long as the condition is known at compile-time. This logic can be applied to the copy-paste operators too depending on the condition.
Async is very experimental and not complete so it's currently difficult for most. It can be thought of as a fancy way of creating a state machine with `.nextState()` being the `resume` keyword making it not a function but by having the compiler perform a little magic at compile-time it's possible to write code which doesn't care about this until you get to function pointers as then the difference starts to matter. If you want a C equivalent have a look at https://c9x.me/articles/gthreads/intro.html which covers it pretty well and it's almost what zig does for you with the `suspend` and `resume` keywords. `async` and `await` should be understood in terms of `suspend` and `resume` as they exist at a much higher level and can be a bit tricky. Another way to get a quick overview of what it's doing is to have a look at generators in python with `yield` and their equivalent with writing the state machine yourself as a class as it's equivalent.
If you feel like trying it again, don't be afraid to reach out on the zig room on matrix. I'll be happy to help with comptime and related things as it can be a bit difficult to grasp.
Oh and don't give up! The best parts about learning is the rewarding feeling when it finally clicks after all the hard work.
No. The reason is everyone who works with Rust chooses to do so, because it is new and jobs are far and few, as opposed to the billions of legacy code lines in eg C++ that nobody likes but companies need maintained.
This comes across as rationalization and investment bias. Because someone "likes" something doesn't make it better, it makes it philosophy or religion.
You can put guards on a razor blade, but it's still going to slice your finger.
Reading the section on how the author just likes C much more than other languages, I was going to comment he should try building his own C-inspired language that he feels comfortable with but fixes some of C's flaws. But it turns out that's what he's already doing: https://gavinhoward.com/2018/08/pl-design-1-principles/
466 comments
[ 4.6 ms ] story [ 307 ms ] thread> I can write C that frees memory properly...that basically doesn't suffer from memory corruption...I can do that, because I'm controlling heaven and earth in my software. It makes it very hard to compose software. Because even if you and I both know how to write memory safe C, it's very hard for us to have an interface boundary where we can agree about who does what.
So like Lisp, C might end up being a solo programmer’s tool too. But not because it is a breeding ground for DSLs but because everyone is adhering to different conventions in the absence of the compiler enforcing one.
What you said is:
> I can write C that frees memory properly...that basically doesn't suffer from memory corruption...I can do that, because I'm controlling heaven and earth in my software. It makes it very hard to compose software. Because even if you and I both know how to write memory safe C, it's very hard for us to have an interface boundary where we can agree about who does what.
And I stand by my point. Single-responsibility principle will get you covered, yes, even in C.
> I actually am not asking for your opinion --
You could show some respect.
> I'm asking for a link to the implementation itself. The details here matter
B-tree btw is a silly example which particularly is not among the hard design problems out there and there are dozens of implementations laying around. Obvious examples to look for would be in transactional database systems implemented in C.
> (and indeed, that's the whole point).
... and the point not being quite clear, even after politely asking for a clarification, so perhaps next time you don't try to play the authority but try to provide an actual example to support your otherwise questionable claim. I'm sure Rust community will profit from individuals like yourself.
[1] http://blog.rongarret.info/2023/01/lisping-at-jpl-revisited....
I love the idea of Rust, but find the syntax to be just awful, and the entire thing just overly complex. I'm not saying that Rust isn't the way it is for good reasons, but the code is often hard to read. Some of the syntax choices are in my mind questionable and serves only to have Rust programmer appear smarter than the rest of us.
Rust shouldn't try to accommodate people like me, because it will make everything worse for everyone. Instead I should look elsewhere, may not to C, I'm not that clever, but to languages like Go, Nim, Crystal or Hare. It's important that we have choices, because even the best designed language, and I do consider Rust a well designed language, will fail to appeal to everyone.
Nobody SHOULD use Rust.
Some may use it, but nobody should.
And because of this "should" many will not use it, especially when you consider that the language is conceptually overweight and there was already one "very popular" conceptually overweight Scala language, which now only the old people remember (laughs).
Getting hung up about the choice a single word in a comment is a little weird and makes you appear rather annoying.
You can use search for the next criteria: why you should use rust
This informational noise is very disturbing and forms a somewhat distorted perception of reality, because it profanes the choice of a very complex instrument (which is just a small part of the huge instrumental set). And thanks to this noise, the obvious boundaries between the apparently different "must" and "can" are lost.
I only talk about this and nothing else.
Because very little of what people complain about is actually syntax and is actually semantics.
But closure syntax is awful
Overall, it feels like a nice combination of Pascal and Python. At least, reading Nim code is usually easier than Rust.
On the other side, I'm still interesting in learning Rust. There are some open source OS kernel projects written in it.
To be clear, I don't mean to disparage the author's decision. Programming can be an artistic endeavour, where personal enjoyment trumps most concerns. If programming C is what keeps you motivated, go ahead and use it! Just be aware that this may make your software risky to use and rejected in many real-world situations.
If that was remotely true how do you explain the Actix episode?
Except that this was not a Reddit problem, was it? This was a problem created by the Rust community that showcases exactly the "Rust Evangelism Strike Force" behavior we're talking here.
Would it made it a Mastodon problem if the Rust fundamentalists opted to use that to drive their relentless attacks? It wouldn't, would it?
I mean, it surely wasn't a GitHub problem when these Rust evangelists started harassing the maintainer through bug reports, which was the place where it all actually took place.
People threw a fit, and it got patched.
Then afterwards it seems maintainer didn't accept any (unsafe?) patches and was harrassed by Rust Reddit community to the point of leaving.
By "people threw a fit" you mean the Rust community put up a relentless harrasment campaign targeting the project author, even to the point where they involved their employer, and ultimately forced the maintainer to give up on FLOSS and abandoned the project altogether.
I mean I saw it on Reddit, guy got panned for abusing unsafe and bunch of unsafe were patched out. That's it as far as what I am aware of.
> Rust community put up a relentless harrasment campaign targeting
Don't judge community (especially one you don't know) by their worst member.
Rust community didn't all agree and made a pact to harass/dox that guy. Few assholes did.
Trying to generalize this behavior to entirety of Rust community is disingenuous. Imagine if I started saying all programmers were wife killers, because some programmers killed their wifes.
The community was massively represented in that harassment campaign targeting Actix. We can still read the threads and the bug reports that fueled that shit show. Things got so bad that distinguished members of Rust's core team felt compelled to put on their PR hat to work on salvaging Rust's reputation and do limitation.
It was not bad apples. It was a hallmark of Rust's community.
Also, sure Rust people care overwhelmingly about Rust crates.
Any large enough community will statistically have some percent of "bad apples". You're arguing as if those bad apples are a majority. And not a statistical footnote.
You mentioned Reddit. Rust's subreddit had at the time over 80k subscribers. The subreddit massively piled on the maintainer of Actix. This harassment campaign was so massive that core Rust members felt obligated to write public declarations how Rust should invest in community building and, with the Actix case as an example, whether they could actually reject contempt culture.
It was not an isolated bad apple. Rust's community is renowned for being toxic, hostile and abbrasive. This topic is underlined time and again even in HN. It's not possible to hide this fact.
First. Rust community != Rust Reddit community.
Second. You haven't made an argument, why should we judge 30k or 80k people on basis of how many harrassers?
Third. What other incidents were comparable?
Fourth. Rust community can be toxic but so can HN. HN has a huge Rust hate boner. Any Rust thread on HN will have people repeating tired Rust tropes.
* https://www.theregister.com/2020/01/21/rust_actix_web_framew...
* https://news.ycombinator.com/item?id=33391344
The Rust community just picked it up. as self-deprecating humor.
I like C as well, but I never kid myself into thinking that discipline and documentation can replace compiler restrictions. I've written large codebases in C, sometimes alone, sometimes in a team, and despite DECADES of experience and meticulous attention to detail, I STILL find the same old memory errors in my own code.
C cannot be made safe because you are not a god.
I mean this is demonstrably untrue: SEL4 is written by mere mortals, in C. It's safer than rust, there are formally verified security guarantees that include and go beyond the memory safety of rust.
It is the case though that c is an incredibly shitty language to do this sort of static analysis on because for example it's easy to do shenanigans like lexical macros that make the static analyzers job much much harder.
But fine. With that kind of dishonesty I could claim that the Linux kernel is written in nothing: I have compiled it and deleted the source code. Look ma, it’s no-code kernel.
[1] Kind of a bummer though that you cannot change any of the C code any more now that the verification code is gone.
If you used something else, you'd need to trust a lot more code.
There's CakeML, which is a formally verified subset of Standard ML [0]
[0]: https://cakeml.org/popl14.pdf
Funky macros can still make static analysis more complex by introducing layer(s) of indirection of course.
But that's not a safe language; that's a safe work. ANY programming work - regardless of language - can be made safe via such expensive means, which puts this square into the space of "argument ad absurdum". You could argue that brainfuck or assembler can be made safe via these means, but everyone would rightly ridicule you for it.
Fuzzing wasn't mentioned in the article but the author does at least to some extended in their project, from what I could tell at a glance.
Fuzzers frequently also use instrumentation to try to figure out inputs that will hit more code paths in a more intelligent manner.
I fuzz religiously. I use it to generate lots of my test suite. I love fuzzing.
I've added a note about it to the post.
People arguing that "you can not ever write safe C" might also come to the conclusion that one "unsafe" somewhere in your Rust code potentially means that the whole code base is immediately at the same level of implicit memory safety as C.
No, the forced use of "unsafe" in Rust for some kinds of safety mechanisms to be disabled makes the code to audit easier to locate and reason about.
IF the code in the "unsafe" block does not actually isolate the "unsafe"-ness THEN conclusions can be drawn about the rest of the code.
Contrast that to C where the code introducing unsafeness, UB and unsoundness could be basically anywhere instead of a convenient "rg/grep unsafe" away.
I was just pointing out that, in the case of the particularly diligent OP, he is probably getting an equivalent level of safety - for much more effort though.
Even with just static analysis and some sane coding conventions you can get C on the same safety level of Rust. I see Rust as a huge waste of everybody's time and resources and far too opinionated in many regards.
Either way, x86_64 assembly code is a high level language that gets interpreted by the CPU at runtime, so not sure how relevant it is to anything low level anymore.
Not a perfect test suite, but the one for my bc [1] is pretty good.
[1]: https://git.gavinhoward.com/gavin/bc
For instance, I worked in a company that rolled its own coroutines in a way very similar to how they work in Go on top of many other things they did to the language runtime. Valgrind couldn't "understand" what was happening in that runtime at all and if you tried to run it would spew millions of warnings (similar problem you'd have if you used Bohem GC with your C program).
There was a way to "teach" Valgrind to understand what was going on in that runtime, but and even though we tried, at some point we realized that it would take too much effort to do so.
I'd bet that OPs home-made alloca() would have a similar effect on Valgrind too.
I was only trying to defend the article author from the accusation of hubris. If all of these tools are indeed used until they show no errors at all, not even false positives (as the article claims), and if the test suite used to run them is non-trivial, then they are likely far more secure than a typical program written in Rust or even something like Go or C# or Java (which all allow and internally use raw pointer manipulation for things like interop).
Oh god yes. That should be tattooed on everyone who thinks otherwise. “Ha! Unlike all those other people, who are clearly not as attentive as myself, _I_ never make this type of mistake.”
> The final reason is the only one that justifies my decision: my code will be rewritten in a memory-safe language. [...] It’s my own language. It’s called Yao.
> This is the only reason I could justify that decision. All of the other reasons above are excuses. This is the real reason.
> You can bet that I do not want to accept liability for a project written in C. It will be rewritten as soon as possible.
It's in my current project at https://git.yzena.com/Yzena/Yc .
Code is at https://git.yzena.com/Yzena/Yc/src/branch/master/src/alloc/s... , and header is at https://git.yzena.com/Yzena/Yc/src/branch/master/include/yc/... .
I didn't read anything past that, because this is very much a "only way to win is not to play" kind of situation.
Once my own licenses have been looked at and adjusted by a lawyer, I'm going to drop the other two.
Until that point, the other two licenses are meant to make my code radioactive. This is so I have breathing space as I work on the code.
And because I own the copyright on most of that code free and clear, I am well within my rights to license the code under all three.
Neat article, always interesting to take a peak at some ones highly idiosyncratic process.
> "The next reason is that during my time with C, I have developed a custom software stack designed around memory safety."
Oh ok then. Again goes to show how C is deficient in some areas
I get something being fun, but C ends up needing to worry about a lot of details where you could be actually improving the code in other languages
Also if he already has a framework that solves that problem for him, then it's not deficient in that areas, for him, right?
Also to gp, title perfectly matches, pointing out why he believes in memory safety when using C, everytime also pointing out the flaws with his thinking, and why this only applies to him in his completely owned projects. Not sure what's wrong with the critics here.
It's also fun to write data-structures yourself instead of being a mere user all the time. In C you can tell yourself that your take on how to do datastructures is worthwhile and unique (might be a slight stretch) but in other languages almost anything you can do is redundant.
Only if you manage simple buffers and do shallow processing with them
String management, network connections, data structures, this is simpler literally everywhere else
> It's also fun to write data-structures yourself instead
True, but it's more fun in C++, warts and all. And not when time is money
There is no theory.
It's really simple, actually:
If you have a mine-field with an uncountable number of mines, knowing the locations of 200 mines won't help you cross the mine-field safely. If, instead, you learn how to spot areas which are known to be safe, you can, assuming you don't make a mistake, at least attempt to cross it safely (and if you fail to cross it, you can go back and learn how to spot other areas which are known to be safe).
That is the difference between your claim that you need to know all the documented instances of UB and my claim that you just need to know enough defined behaviour to write your program.
It has nothing to do with security assessments or pentests.
It's not just the API either. So once you've got your "nice" error handling API, you have to manually handle resource clean up - no branches that exit for you; every function call is accompanied by a result check and a goto to cleanup code (so your pointers need to be defined properly prior to any calls and initialised to null). Honestly, I don't know I ever lived with this mess. It was a painful and laborious experience to get to something that was just about ok.
> So once you've got your "nice" error handling API, you have to manually handle resource clean up - no branches that exit for you; every function call is accompanied by a result check and a goto to cleanup code (so your pointers need to be defined properly prior to any calls and initialised to null).
I have a stack allocator that will release everything allocated in the function on exit, and I have macros to replace my use of the return keyword that also deallocate everything in the stack allocator before returning.
So my code actually has code cleanup on return.
C does not have to be laborious. Maybe the fact that I have done things like that is part of why I like C?
Basically, you tell the stack allocator to store a jmp_buf, then you setjmp() on it. Then keep going.
For a function, a special destructor is used as a marker. Same with scopes.
The same is also true of a setjmp() destructor, which is actually what activates longjmp().
The stack allocator should have three functions to unwind itself until it reaches the end of the next scope, function, or jmp, respectively.
Then, when you need an exception, or to exit a function or scope, you call the correct stack allocator function to clean up everything to that point. Then you return or whatever.
This can run arbitrary code on cleanup for an item if you write a "destructor" with that arbitrary code. For example, one idea I had was to use the stack allocator to ensure a mutex or other lock was always released. This would be done by storing a pointer to the mutex and writing a "destructor" that unlocks the mutex when given a pointer to the mutex. Boom. Scope-based mutex unlocking.
Makes sense?
Not really. The author describes he put together a framework for C that implements memory safety features.
Isn't it intellectually dishonest to criticize the adoption of libraries that provide features that other languages may or may not have built in?
What's the end goal exactly? Use safety features to write safer software, or attack anyone that hasn't mindlessly jumped onto a particular bandwagon?
Of course you can augment a language with libraries. But you are one developer and everyone else will make different choices.
Sure, you are free to make this sort of claims. But they are besides the point, aren't they?
The cliche being discussed here is C's hypothetical ties with memory safety issues, and here we are commenting on a discussion on how someone writes C using a framework focused on memory safety and thus providing "some guarantees" about their whole ecosystem. Yet, we're still seeing comments mindlessly parotting myths in spite of the facts that are being dangled right in front of their nose.
More importantly, it goes against the cargo cult narrative that the choice of programming language is the one and only factor determining memory safety, and consequently any use of languages such as C automatically leads to security issues.
It's not a coincidence that we have people in this discussion coping with that concept by spinning the use of frameworks focused on memory safety in C applications as somehow representing writing code in an entirely different programming language.
Despite the authors borderline obsessive efforts to write memory safe C code, it's interesting to note that his implementation of "bc" prominently contains a file documenting memory issues in previous versions. If that's what borderline obsessive gets you, how are us normies who are only programming to pay the bills, who have to work with other people who also just want to finish what they are doing or interact with blobs of code written by other people with less exacting standards supposed to write memory safe code?
It's a framework. It's a set of C macros and C code. It's not a new language. Please stop.
> The final reason is the only one that justifies my decision: my code will be rewritten in a memory-safe language.
> No, it won’t be Rust. It’s my own language. It’s called Yao.
If you wanna write your hobby programs in C because you think it's fun, then fine, you do you. But this seems like a pretty good indication that basically no C written by anyone can realistically be truly safe.
If the author's reason is "because I can get the same features as Rust with much more effort" they've failed to justify it and thus that reading.
Even malloc | free aren't vital as memory is preplanned and can be retrieved from an RTOS in a single block at startup.
> Using glibc in avionics software is insane,
As I said above .. there's no need for glibc et al.
> I feel like you are missing the point.
We seem to be in agreement here.
> In other words, it’s asinine to pretend C is somehow more reliable because ...
What most people call "C" is (preprocessor) + (actual language) + (stdc function library).
What I call C (and hey, maybe that's just me) is just the language component, the "below the fold" library spec in the ANSI Std is easily disregarded .. it's more of a dated "proof of concept" of (for example) one of many ways of handling strings.
C is a nice language - the preprocessor and stdlib have issues.
Christ, I wrote it as an absurd notion in the first place. This is honestly like writing “go ahead and wear underwear on your head” only to have someone tell repeated tell you that isn’t necessary. Well, no shit.
Language doesn't matter.
P.S. I see in realtime, reloading the post, the number of downvotes received (the score goes up and then ways down with the downvotes). Another problem is the Rust community. It has a very peculiar way to stay in this world.
But when you start composing things together and juggling the lifetimes of various things, the complexity balloons and it becomes a lot harder to understand "spooky action at a distance"-type things. You might argue that with good structure and discipline it's still not difficult, but the assumptions are all implicit and you have to get into the mind of whoever wrote it in the first place to understand it. And I write a lot of C and still find it tricky when I have to go look at some random C codebase!
Absolutely. C Code is not necessarily easy to understand. Not at all.
But the C parts itself are? In C you seldom wonder what C does, you wonder what the C code does.
If you work like OP likes, single person projects, small enough to keep almost completely in your head, I can see how C is especially charming. You never wrangle with C, only with your own code.
You do realise that C and C++ are among the languages (there is also Perl) having anual obfuscated writing contests, right?
Being self evident is certainly not something I associate with C code.
But usually the most controversial features are:
- use of exceptions
- use of RTTI and dynamic_cast
- use of stdlib types which allocate memory under the hood
- ...or even use of any stdlib types
- has boost or similar complex dependencies
- use of 'modern C++' features that explode compile times (like the range stuff)
...etc etc... when you look at C++ libraries written in the game development or embedded hemisphere, those usually use an entirely different C++ subset than the more academic "modern C++" crowd.
* core, whose only dependencies are six symbols (memcpy, memcmp, memset, strlen, rust_begin_panic, and rust_eh_personality)
* alloc, which layers on top of core, and adds the ability to allocate memory
* std, which includes all of the operating system specific things
But most importantly, you can declare that your code doesn't require std and alloc with an annotation, and there's a way to tag these crates in the package manager as well. Additionally, cargo has a "features" feature, so you can say "please give me the no_std version of the library" and get what fits within your needs.
My team at work is working purely in the 'core' subset, and we're able to easily find and evaluate open source projects that will work for us, because of this.
Oh, I should also mention: while Rust doesn't have exceptions, there's also a way to compile your program so that panics abort instead of unwind the stack, similar to -fno-exceptions being passed when compiling C++ code. There is also a setting you can include so that, if, for some reason, you require the unwinding semantics, if someone tries to build a project with abort semantics and your program, it will not allow it and let you know why. I don't think I've ever seen this annotation on a package in the wild, because since they're not used in the regular course of programming in Rust, virtually every open source library does not use them for important semantics and so therefore works with either setting.
Thoses are really bad footguns.
It's definitely fair to argue that Rust is complex. But this doesn't compute.
You don't like Rust because you're less productive with it so you write... C? Rust is so much more productive compared to C it's not even funny, even with its limitations and extra complexity that are necessary for memory safety. Things that take pages of code in C can be done in a single line of code in Rust.
This starts at fairly fundamental data structures. Need a hash map? In Rust you have one available, out-of-box, with state-of-art performance. In C? Implement one yourself. And good luck getting even a fraction of the performance the Rust one has.
Then there is dependency management and a huge ecosystem of libraries. Do you need to, say, encode an AVIF image? (Something I personally had to do.) Easy. `cargo add ravif`, and two lines of code later you're done.
I can keep on going, but I'm sure you get the idea.
I really honestly don't see how anyone can in good faith argue about productivity and say that C (out of all things!) is more productive than Rust. That Rust is more complex, sure. That you don't like it for aesthetic reasons? Fair enough. But not as productive as C? No way. Perhaps this is where the downvotes you're complaining about come from?
The libraries that make C more productive are out there, they are just harder to find than in the Rust ecosystem.
IME C programmers (including myself) separate a language and its stdlib much more strictly than (for instance) the Rust, C++ or Python crowd, mainly because the C stdlib is pretty much useless for anything that's not a simple command line tool (and because of such restrictions, C programmers also often use a whole language toolbox instead of attempting to write everything in C - Python and C go very well together for instance, right tool for the job etc...).
No. I also mean that the language itself is more productive, even without the standard library and the tooling. (Although, indeed, to a lesser degree.)
Sum types. Pattern matching. No need to bother with header files. A module system. Proper generics. AST-based hygienic better macros. Procedural macros. Stronger type system. RAII. A proper string type which knows its length. A proper array type which knows its length. Unicode support*. Async + await support. Support for closures. Ergonomic error handling (with the '?' operator). Proper abstraction for iterators.
Do you want me to keep going? Because I can keep going. (:
Again, this is more complex than what you have in C. And it is harder to learn. But that's not what we're talking about. We're talking about pure productivity here, so I'm assuming on one side you have someone who has mastered C, and on the other side you have someone who has mastered Rust. There's just no contest that the Rust person will get things done significantly faster than the C person, because the language itself is so much more powerful. (But of course it's a lot harder to master Rust, so the calculus might be different if you don't want to master your tools.)
* - part of Rust's core so also available in `no_std` contexts, so I don't see it as part of the stdlib but part of the language.
I would still argue that none of those language features really move the productivity needle a lot into one or the other direction. A good library ecosystem on the other hand does make a difference.
All in all, I would probably add fewer features to C than I would remove from Rust to get close to what I expect from a performance-oriented systems programming language (Zig is actually pretty close to that ideal, but that's a different discussion).
Ah yes, I won't disagree here; macros get very complex very fast. It's one of the more undercooked parts of Rust.
> e.g. I don't really see the usefulness of async-await in a systems programming language
This really depends on the task, but honestly it is genuinely useful. Even in places you wouldn't expect, e.g. in embedded: https://github.com/embassy-rs/embassy
And AFAIK there are plans to use async Rust in the Linux kernel.
> This starts at fairly fundamental data structures. Need a hash map? In Rust you have one available, out-of-box, with state-of-art performance. In C? Implement one yourself. And good luck getting even a fraction of the performance the Rust one has.
I've implemented a hash map in my code. I loved the experience, and I know more about computers and hash maps now that I've done it.
And my hash map was faster than the one in the C++ STL last I checked. I haven't benchmarked it against the Rust one, though.
Yep, everyone at least once should implement a hash map. Hash maps are so fundamental and so often used that it really pays off to understand what makes them tick.
> And my hash map was faster than the one in the C++ STL last I checked. I haven't benchmarked it against the Rust one, though.
The one in C++ is very famous for being really slow, so this is a low bar to clear. (:
Not sure if someone directly compared Rust's hash map to C++'s, but Rust's hash map is based on Google's SwissTable, so you can probably assume it performs roughly the same. Here are some graphs: https://youtu.be/ncHmEUmJZf4?t=2093
There's nothing wrong with the C++ hash-map. It's a different trade-off which makes perfect sense for something that is part of the general-purpose toolbox. See my response above for more details if interested.
Last time I checked, benchmarking hash-maps, or FWIW any other data structure, in the vacuum did not make much sense to me. Microbenchmarks cannot mimic or predict end user workloads well enough, at least in the cases where it's worth it.
That said, each implementation has its own trade-offs and in particular the one from C++ STL opted in for the pointer stability. For that reason alone, which I think is quite worthy, open-addressing conflict resolution is out of the question because it cannot give such guarantees.
With that in mind, if you're ok that a pointer to your key-value pair can all of the sudden be _invalidated_ by the mere operation of inserting or erasing yet another element from your hash-map, then of course, you can trade robustness for a more cache-friendly conflict resolution algorithm. But then again, this says nothing about the potential performance improvement unless you're able to measure it.
I pretty confident that this would go away if you write Rust for a few weeks.
I remember when first learning C, I struggled with some things too, but then they just clicked. It's probably similar when learning any new language (especially if they add something novel)
Just a random recent example: call an async function for a side effect without caring what its result is, Rust yells at you that the result must be handled "`#[warn(unused_must_use)]` on by default", so you'd think I would just annotate with `#[allow(unused_must_use)]`, but no way, you must store the result in a _ variable: "let _ = my_async_fn().await;". It's a great language, it's just ugly, sorry, not sorry.
[1] https://www.youtube.com/watch?v=rAl-9HwD858&list=PLqbS7AVVEr...
[1] https://doc.rust-lang.org/book/ch18-03-pattern-syntax.html
Fun fact: Google-ing "syntax ugliness" gets as first result "Rust’s Ugly Syntax", https://matklad.github.io/2023/01/26/rusts-ugly-syntax.html
In that case, yes, let _ is the usual pattern. But it’s rare that I ever actually want to use that. Typically I either return the error upwards or just unwrap. But if you truly don’t care about whether the operation was successful you can indeed ignore the error.
There are plenty of pain points with Rust but I don’t really think this is one.
Btw, the article you linked argued against the claim that Rust’s syntax is ugly, and instead claims that Rust’s noisyness is due to its semantics, largely in service of performance (which is why production C++ code is often similarly ugly).
For me Zig looks like something that could become a new C. They aim for great C binary and source compatibility (both ways - called and callable from C). It’s planned to be small and lean. And avoid preprocessor and UB as much as possible.
C++ is not a bad language at least because lots of amazing soft already written on it, but some people accept it with all of the myriads of complex rules with their own subset and some people totally disagree with this and just want Better(Modern) C with plain and simple rules without hidden complex things which happened behind the scene, and counterintuitive features.
About Rust is everything cool except sometimes it doesn't allow you to write correct code and push you to do things in ways that you don't like. But we are programmers and some part of us is a painter :)
Here's why. In spite of having quite a bit of experience with many functional and imperative languages before, and picking up new languages used to be a seamless experience. But Rust turned out to be an extremely frustrating experience. I was feeling like I couldn't tell the compiler to do what I wanted it to do any more. I took it as a challenge, didn't want to give up.
And when the first non-HelloWorld app actually got to compile and work (it was probably IPTrap 2, that implements a userland TCP stack), there was this feeling of accomplishment that I couldn't help myself sharing with the rest of the world.
Fast forwarding a couple years... I don't enjoy Rust any more. I still maintain Rust code, but it's a pain, not something I'm having fun doing.
First, memory safety in Rust is overrated. What it actually brings over GC'd languages is that two threads can't share the same pointer at the same time. Nothing else. If I'm not using threads, it doesn't matter. It's not any safer than PHP, Go or JS, but is far more complex. I won't provide memory leaks nor deadlocks either. To some extent, it actually encourages them (I'm often blindly adding Arc, Box, and Mutexes just to silence the borrow checker, not because they are actually needed).
Next, readability is not great. Too many sigils, and the language got more and more complex and less and less readable over time. I'm frequently having a hard time reading my own code after a while. Even more with other people's code. It often feels like people try to maximize the number of language features being used, so that the source code looks impressive, rather than keep the code basic and simple.
Finally, the ecosystem is very unstable. 3rd-party crates (even essential ones such as for error handling, due to limitations of the standard library) are constantly deprecated, abandoned, or having braking API changes. I gave up maintaining some projects, and keep using abandoned (even with security issues, such as old versions of hyper) with others, because the maintenance cost is too high.
After using almost exclusively Rust, choosing Go for a new project (dnscrypt-proxy 2) was enlightening. There were probably runtime bugs that Rust could have detected at compile-time, but overall, productivity was way higher. Making a change is straightforward, I don't have to think about the implications on the borrow checker. The compiler is fast. Performance is fine, and I can spend more time optimizing the actual algorithms than fight the compiler. Go tells you at runtime, not at compile time, if locks have been forgotten. But otherwise, memory safety is comparable to Rust. The runtime gracefully catches out of bound accesses to slices, null pointer derefs, etc.
I also still enjoy writing C code. It's good to have some freedom and control. But C also has rough edges. UBs, portability, minimal standard library.
Zig fixes many issues that C had, and is the language I really enjoy the most now. It's fast, portable, easy to learn, easy to read, has proper error handling, strong typing, catches out of bound accesses to slices and does checked arithmetic, and, overall, is a joy to use. Not to mention its seamless integration with C.
It doesn't have temporal memory safety, but, like Go, Zig's simplicity helps me reduce logic bugs, and focus more on algorithms than their implementation, overall resulting in better code.
Rust profits from "rewrite it in Rust", since it is more fun. But so does Zig.
Still, I am worried about the ecosystem, since every time I pull a bigger crate through cargo, I have to think of npm and the sad state of JS ecosystem. Also, I have a small, rewrite from python project at work and I am also worried whether it will be easy to upgrade in 2, 3, 5 years? If I write it in Java, it certainly will...
Fully agree on readability. It is not that Rust is unreadable, but a lot can happen in the background and you have to look hard for small sigils, presence of semicolons and blocks.
Otherwise there is Go, Haskell, OCaml, Java, Kotlin, Scala, Clojure, Lisp, Scheme, F#,...
If Redis works then the proof is in the pudding, I guess. But as an average/below average programmer I’ll stick to something more modern when I need that level of control.
Another possible conclusion is that your program design is just not as simple as you thought it was, and Rust is pointing out why. This drives you to make it even simpler, which is arguably good.
I feel I have to develop an economy of effort in C because there usually isn't a dumb and easy way to do things - this pleases me.
C has a mental model that seems relatively simple - I feel I can reason about what's going to happen to a greater degree than higher level languages.
C++ is the worst IMO because it has lots of abstractions but they don't make programming simpler because you pretty well have to understand the low level aspect of what they're doing to get performance - so the complexity is much greater.
Rust seems to put a lot of difficulty up front which is of no value in the kind of typical short program I write most of the time. I just didn't enjoy it. If I had something complicated to do I might eventually appreciate it.
The more high-level the language the less one tends to understand it and that's unsatisfying.
Having said all of that though, I tend to "just write python" whenever I can. If it's a throwaway program one doesn't need more and if it turns out to be necessary to interface to some library or other I know C well enough to sort that kind of thing out. I use threading.....never - at least never by choice.
Ex: https://github.com/TkTech/pysimdjson/issues/73
Overall this is way better than writing everything in Rust.
In re Rust: I feel one of the mistakes most people make is taking Rust to be a general purpose "default language" for everything, like C and C++ used to be. But like you wouldn't write C++ for most stuff even if you were magically granted the ability to never ever write unsafe code, writing web services in Rust just means making your own life harder. It's a great tool, I'm happy it exists and I follow its development, but we must accept it exists in a different space than, say, Python or Ruby or Javascript.
I feel physical pain as I say this, but most stuff should probably just be Java.
This has been on my mind a lot lately, causing similar anguish.
I'd note that, from what I hear, C# would also fit the bill, but be as unappealing to me.
I really wish that there was a language with the productivity, ecosystem and ergonomics of Python, with the speed and efficiency of Java and C# and a type system that increases productivity rather than word count.
Go seems... fine, but, I dunno, slightly regressive. I'll probably use it where performance and scaleability matter.
That would be.. Java :D While I’m quite a big Java fan, for the other parts some other JVM language can easily fit the bill and you even get a choice. Scala, Kotlin, or maybe even Clojure if you don’t mind dynamic types.
The others look like really really nice languages and I'd love to use Scala for a solo project.
However I guess I need something that's a) truly general purpose b) not going to scare away collaborators or potential hires c) going to be around for a long time d) not going to require evangilization at, say, a start-up.
Kotlin... I guess I'm wary of it being the new Coffescript - i.e. that it spurs Java into improving to such a degree that it removes the need for Kotlin in the first place.
And yes, having just spent a long time transitioning a project from JS to TS, I'm not sure I want to start anything serious without at least half-decent static typing - a major reason I'm looking for an alternative to Python, which is just not quite there.
I think that's unlikely. The key difference being that Coffeescript was always dependent on JavaScript because it compiled to JavaScript! Whereas Kotlin doesn't compile to Java, it compiles to JVM bytecode the same as Java does.
Scala is another alternative with more scientific approach.
Both enjoy JVM quality and consume Java libraries.
Java is improving as well, but not on par with those languages. Worth checking out though.
I've never touched android development in my entire life.
https://en.wikipedia.org/wiki/List_of_JVM_languages
Kotlin has its future assured thanks to godfather Google, stagnating Android Java on purpose (only recently they decided to finally move to Java 11 LTS subset), while promoting Koltin as its replacement for everything besides core platform.
So no wonder that people usually say that.
Unfortunately, when I use Dart, it's on Android, where the SAF exists, which kills my enjoyment, despite Android/Flutter otherwise being probably my favorite platform.
Try OCaml or F# then.
[1] https://nim-lang.org/
Nim has that except for the ecosystem. And it's also kind of janky, enough that I got so frustrated I stopped trying to use it. But you might have better luck than I did; check it out if you haven't.
That's a terrible recommendation!
If they made iterators work how they seem they should work, I would come right back to Nim. It does so many things right but this one thing is a dealbreaker for me. The UFCS is practically begging me to chain lazy iterators together but actually trying it is like stepping on a rake.
Lazy iterators and generators are something I've missed in Rust. Haskell has them just by nature of everything being lazy and I love that. But I've become too familiar with Python, and unless runtime performance is a bottleneck, Haskell hasn't been the path of least resistance for most of my practical problems.
Just Python and shell scripting. All this stuff is for hobbyist programming not my day job, so I just shelved the stuff that need performance and dusted off some projects that don't. I'm learning Rust at the minute but it's slower going, I'm not at the level where I can make the stuff I want.
Probably influenced by the fact that I hate debugging in production and last times I wrote some serious Python code, I ended up doing just that.
YMMV
I don't hate Java, I work with it every day and as you say it's a practical language that gets stuff done with minimal hassle.
The reason why it's hard to stomach is that it's the infrastructure of the future with the programming techniques of the past. Java the environment is like living in 2050, Java the language is like living in 1990.
Things are slowly getting better, but everything feels clunky and bureaucratic. The type system manages to be simultaneously ultra-verbose and not expressive enough to represent useful properties. It's a complete travesty that I need to codegen my own Tuples and write my own Either<T1, T2> as a literal Bohm-Berarducci construction. No value types (they're coming, but not yet stable). Didn't have Records until Java 18. Useless semantics for "final". Getters and setters. Nulls aren't even funny.
The language design feels at odds with the broader goal of providing a safe, efficient, solid programming environment.
It's still a fine choice for most applications, I'd venture to say it's actually the best choice, but that's because the language itself actually matters very little, unlike us nerds would like to think. But it doesn't have to be that way. I'm hopeful it'll get better.
Wonder what are the examples of 'short programs' where you'd just zip out raw C as a default language, and it would be simpler/faster to implement than in Rust?
Using C for anything is just rare for me and C++ and Rust even more so because they don't add anything that I need usually.
I've found myself thinking, time and again, is Rust exposing, to the programmer, what have traditionally been details handled to varying degrees by sophisticated compiler optimizations? If so, a great optimizing compiler might deliver binaries as performant as a Rust expert could, but with less programmer effort?
(I can't answer that, only haven't found a counterexample as a one time optimizing parallelizing compiler person.)
- who owns what
- how long things live
- which values might be null or missing
- which functions might return errors
- how strings are encoded (https://twitter.com/timClicks/status/1450943515635056648)
- which types are thread-safe
There are some performance gains to be had in that list, but for the most part it's about asking the programmer to be explicit and avoiding convenient-but-risky defaults that lead to lots of bugs.
There won't be pointers, and I think everything will be passed by value.
I'm not sure that compilers can optimize this and "move" returned data. But the goal is to greatly limit side effects and improve cache locality by encouraging the coder to use more functional habits instead of oop.
There are grey areas because I'm not sure the design is sound, but even if it's not, it can be safe and fast enough.
I didn't say it's a good idea… Isn't that how PHP started off?
> Yes, actually, but please hear me out for the rest of the post. I have reasons, I promise.
This is the kind of honesty that I live for.
The author adequately defends their predilection for C, which was never doubted; they don’t produce a great argument for why C is good (which, perhaps, they didn’t intend to). Most of the defensive programming techniques covered in the post reflect this: asserts, for example, only catch the bugs you know about.
There are all sorts of valid arguments for not immediately porting over the huge legacy of C/C++ code bases to something less likely to blow up in your face. However, the amount of valid arguments for starting new code bases in those languages is rapidly declining. I'm sure there are some corners where you just have no other valid choice and that's fine. But increasingly it's just a matter of people being stubborn, rigid, and unwilling. Half the success is just understanding your own limitations and mitigating those. The rest is just dealing with the inevitability of people messing up.
> they don’t produce a great argument for why C is good (which, perhaps, they didn’t intend to)
Never intended to.
If we gradually destroy all semblance of individual thought and discretion and replace that with something that essentially approaches rule-by-machine, what remains is not far from an authoritarian dystopia in societal terms. Even more so when you realise the direction Big Tech is taking. Jailbreaking and rooting are an escape hatch that they're certainly trying to close with all the "memory safety" stuff.
C also has an advantage of being simple enough that a single person can write a compiler for a nearly full subset of it. The same can't be said of many other languages.
I'm not saying I can write 100% correct code, although I certainly try (and IMHO it's not too difficult to get close.)
"Freedom is not worth having if it does not include the freedom to make mistakes."
"The road to hell is paved with good intentions."
Apparently the road to hell is paved with vague slogans...
C is actually a fantastically complicated language, both syntactically and at the semantic and abstract machine level.
i am not sure i understand, are you saying that c and rust have the same semantics ?
Treating C like a “high level assembler” needs a rule like the cryptographic doom principle: it will inevitably lead you astray, and “astray” for C generally means memory unsafety.
you are right. however, i think c does a better job at being a model of an abstract machine than rust. if we restrict semantics to that, and the developer accepts that they need more knowledge than just c, i think the semantics are simpler than rust's. in the same way, dont you think that rust can create a false sense of security to those not completely understanding what protections it provides? if we see programming languages as a hci problem, then i think c by default offers more freedom, and consequently more unsafety. amyway i dont want to come off as hating rust, i think it is a good language, but more importantly its community seems fantastic and i wish it success
No: the entire point of Rust's design is that you don't need to know the abstract machine semantics to write Rust safely. The compiler enforces the semantics for you, rather than pushing them onto you.
If we are being honest with ourselves the truth is that even a language as nightmarishly complex as C++ is preferable to C. And whether or not you agree with that the fact is that the world marches on regardless.
Am I dreaming?
The ability to write your own compiler for a language... that's related to the above?
except... Forth or Pascal, perhaps?
If they were, things wouldn't be the way they are today. Perhaps you're saying that they are, but can only express that position in the form of subtle exploits (which seem to be decreasing over time)? No, I think the majority of engineers are overwhelmingly complacent and obedient, and won't oppose.
As for why Rust, that's because the amount of prosletysing of that language seems to be far more than all the others.
Rustacean Abraham Lincoln might quibble on what freedom means in this context: https://news.ycombinator.com/item?id=34620164
To put it in other words, I want my tools to do whatever I want them to do and not the other way around.
Rust just says "This is an engineering project, not a philosophy paper, or science lab. It is not the time or place for experimentation, new ideas, and individual discretion, or for trusting each other or ourselves, we want reliability at all costs".
Which is exactly the attitude I want to see, when failure means I lose important notes, money, or get lost because GPS crashed, or my battery runs down because everyone was too afraid to optimize their brittle bug heaps, or a self driving car murders me, or we return to 1970s level tech due to nobody trusting software.
I'm rather fascinated that you put this in these terms, because I've seen the exact opposite (specifically the philosophy paper). The few times I've seen rust tried out, it seemed to turn into a competition between devs about how clever they could be with category theory. Then an abstraction stack grows until logic errors creep in because nobody understands what the thing is actually doing anymore.
I don't want to blame rust specifically for this, because the same thing happened with scala. Not sure what it is, possibly just a lack of dev discipline. I can't help but view this category of languages (rust, scala, haskell, etc) as competitive philosophy because I have to spend several hours sitting around pondering the nature of things before I can make minor changes from my jira ticket.
End result ends up being that someone usually re-writes an "interim" solution in java/go/etc, over a couple of sprints and then nobody ever gets back to the long term solution written in the elegant language because they don't have time to understand it.
They're all plenty skilled, but I'll give you the pragmatism one. A certain style of dev seems to have a compulsion to violate Kernighan's Law.
Eventually that abstraction hierarchy collapses on itself. I don't want to pin this purely on rust either, since I've seen it in plenty of languages (including java), but I see it happen often enough that I'm reluctant to trust my co-workers with rust at this point.
Or maybe they all can and I'll retreat into my little abstraction-minimal kingdom where the code's not very elegant, but I can figure it out, and I can onboard people in less than a month.
>> "Freedom is not worth having if it does not include the freedom to make mistakes."
>> "The road to hell is paved with good intentions."
Having a smarter compiler that prevents errors does not reduce individual thought or hinder creativity.
It incorporates best practices and discipline that should be followed anyway into the creative process to prevent errors at the source.
"Freedom isn't free."
"The price of freedom is eternal vigilance."
In the 90's it was very common that big shops that did some programming would have their own languages, which continued into the 00's in the form of everyone rolling their own C++ standard library. I had the "pleasure" to work with both. Invariably, this stuff was so much worse than off-the-shelf stuff and yet the authors believed themselves to be geniuses.
I mean, there's a chance this guy did something outstanding and actually improved something about C, but so far I've seen so many fail to do that in such a predictable patterned way I just don't want to see any more of that.
My favorite C code is the one that has no macros, as few of typedefs as possible, no acrobatics with alloca() or any platform-dependent stuff. I think, FIO is a good example of what I'm talking about.
The whole thing just sounds like "yeah, it's a swamp, but it's my swamp and I like my swamp!" kind of thing.
I know this. The only reason the custom stuff works for me is because it works for me. I accept no outside contributions, and this is one reason why.
It sounds like you're talking about using libraries as if it was a bad thing.
> had the "pleasure" to work with both. Invariably, this stuff was so much worse than off-the-shelf stuff (...)
Where do you think "off-the-shelf" stuff comes from? It sounds like you're trying to mistepresent the adoption of subjectively sub-optimal libraries as somehow the issue caused by using libraries and frameworks.
> mean, there's a chance this guy did something outstanding and actually improved something about C, but so far I've seen so many fail to do that in such a predictable patterned way I just don't want to see any more of that.
You're showing a tremendously naive and misguided belief that libraries are somehow bad.
Languages such as Rust flourish with the adoption of third party libraries,some of them euphemistically described as "not stable". Languages such as C++ flourish with third-party components like Boost and POCO. But applying the same principle to a language like C warrants an automatic and mindless putdown like blindly accusing anything of being bad? It makes no sense.
Many people that go off the beaten track often fall into the same ditch.
Third-party libraries were not born out of the thin air but because of different parties having different and very much often disjoint requirements so it's not particularly NIH syndrome.
You're somehow trying to imply that libraries are not reusable if they are not widely shared, which makes no sense at all.
The best argument you could make is regarding stability, but you simply cannot lay any such claim just from the library's licensing.
Accusing internal libraries of being half-baked, badly designed, or bug-riddled is just a cheap blanket putdown.
I don't see the relationship... how do you make such a leap?
> Where do you think "off-the-shelf" stuff comes from?
I worked on proprietary projects that eventually went public domain. So, I can tell you where off-the-shelf comes from from personal experience. Proprietary in-house tools have the benefit of not having to deal with many things that an off-the-shelf tool would have to deal because they can choose the kind of hardware to run on, the kind of supporting libraries, their versions and combinations to use, the kind of developer tools to support and so on. off-the-shelf tools, in order to be successful must cover a much wider area of ecosystem in order to be relevant.
Here to give you a better example: C doesn't have its own concurrency primitives, no concurrency model etc. But, you could build an extension, let's call it CC to have some sort of concurrency based on pthreads library. If, for your internal needs, you only use CC on Linux -- you are golden and everything works fine. Once you try to make CC into off-the-shelf product you have to do something about pthreads missing from other platforms.
Off-the-shelf products are typically born as in-house products, but they need an extra step to become what their name implies.
In the specific case of what OP described as their own extension to C, I can vividly imagine a language that doesn't cut it as an off-the-shelf one. Making an off-the-shelf language would've also guided OP to a much larger departure from C because memory safety is only one of the big problems with the language. And this is what happened to a bunch of other languages which already walked a big chunk of that departure journey. This is why I would've been frustrated having to use the language like OP's: it would've felt like not enough change and too much headache to adjust to my target environment than could be had by using actual off-the-shelf languages.
> You're showing a tremendously naive and misguided belief that libraries are somehow bad.
I have no idea where you get this from. If anything, I say the exact opposite...
You've just outlined a perfectly good subset of C. I'm pretty sure people who write C extensively even to this date mostly end up there. The beauty of using C these days is that one gets to control every single byte without going through hoops and loops. Also, any C code can be reliably integrated into other languages using FFI, which is quite universal. So, yeah, no need to create dirty hacks.
No, not even close: Struct padding, compiling switch as if-else vs. jump, implicit arithmetic promotion to int, stack layout of local variables, and many other issues.
If you want to control every byte, write in assembly language.
Quite literally.
The F-35 software is C++ code:
https://www.stroustrup.com/JSF-AV-rules.pdf
- It’s their subjective opinion (the author makes that clear)
- The author works alone
So there’s no risk that one would have to work with this dialect.
Don't worry; I do not accept outside contributions, so this code will not be imposed on anybody but myself.
But it works for me. It fits my brain really well.
Arguably, you don't. The big difference is whether your compiler enforces and tracks your documentation, assumptions used by your code, often critically important for correctness. Unfortunately, Rust is far from being perfect in this regard (e.g. SPARK probably does a better job here), but it significantly improves on C/C++ in this regard.
>I Work Alone
Unfortunately, even if you are the only soul touching the code, it often quickly becomes false. On a big enough project, "alone" inevitably becomes "me and that guy who wrote this part of the code". This also can be formulated as "me and 2-years-ago-me". In other words, we quickly forget a lot of context used for writing a piece of code.
>Rust is too big for my small brain, unfortunately.
I think the lack of hubris, the understanding that small human brains are not enough to keep track of all necessary context required for writing correct software is the key element for liking Rust and similar languages. Rust is not about building ivory towers of abstractions for the sake of it (though some people try to walk in this direction), but about offloading as much as possible of the context on compiler. Unlike humans, the compiler does not get tired, it does not forget. Yes, it's limited and because of those limitations it requires to restructure our code and sprinkle it with additional information, but more often than note it's a good thing.
As sel4 and other formally verified software demonstrates, it absolutely can be done in C. But one could say that C used in sel4 is not a C language anymore. After all, you can not take a random C library and use it in sel4.
1) Why are we explicitly directing a loop forward or backward most of the time when it's not necessary. The compiler should direct the loop unless we want to explicitly do this for the body of the loop. My thinking is that there may be performance reasons to reverse a loop or even split it into separate threads. (unlikely)
2) In C++, I've always wondered how template types get instantiated for POD that is represented the same at the bit level. Does the compiler duplicate the templated code or is it smart enough to say "this std::vector<int> is the same as that std::vector<intalias>". Rust prides itself on zero-cost abstractions, I have wondered if they reduce binary bloat in this area. (as a naive C++ person)
2) It duplicates, and the standard even comes with asinine "guarantees" that actively sabotage deduplication, such as requiring all functions to have different addresses. Which means the compiler can reuse the same function, but only one function label can start right at the code and the others need to NOP-slide or JMP into it. Which unduly pessimizes precisely those functions that are most likely candidates for deduplication: Those that themselves only consist of 2-3 ALU instructions.
And this goes without even mentioning LTO, which has been explicitly designed for optimizing the code layout and size during the linking phase.
So, code deduplication is a real thing and happens regularly with your code. Templates are no much different than the non-templated code written for the same purpose.
But without LTO, it's impossible for the compiler to know whether a non-inline function with external linkage will be called or have its address taken, so the compiler must ensure that it has a unique address.
The point stands. Compilers cannot merge equivalent functions in cases when it makes sense to even think about this optimization, which is when it actually has to export an externally visible symbol for the function.
As for why you sometimes do one over the other. I think it is true that computers branch on zero more easily than other values. That said, I'd be a little surprised to know it makes a difference that can be measured in any meaningful program.
Loop inversion, unrolling, and splitting. The compiler can do so much with a loop to make it run faster on 'this machine' to optimize for speed, memory use, or binary size.
I love that Rust has eliminated the egoism around "you should know at all times when something is allocated or deallocated". We're trusting the compiler to do that better with zero cost to us. So take it to the extreme: Only specify the loop direction if you have a true requirement to do so.
This is my Ted Talk, thank you.
For #2 - LOL! I had no idea it was essentially aliasing those functions to get around a requirement for uniqueness.
> I’ve written code in so many languages:
[omitted list of a couple dozen languages]
> and many more.
> When it comes to the enjoyment of programming, I hate all of them. With a passion.
> Except for C.
I get the sense that this is the real reason, and the rest of it is justification that they understand the risks involved but choose to do it anyways. And honestly, I think this it's fine? I'd argue that it's a little inconsistent with their statement above that avoiding C/C++ in lieu of memory safe languages and should be followed by default, but they subsequently admit to being hypocritical about this, so I'm not really concerned that they don't understand the benefits of memory safety. When it comes down to it, I have middling confidence that I would be able convince someone who still at this point doubts that Rust is safer than C, but I have absolutely no illusions that I can convince someone to change a personal preference. If anything, it's a bit refreshing to see it freely admitted, given how often it seems to be the real reason for a lot of choices of what language to use (and I don't just mean this for C/C++, but for any language, including Rust).
We use rust and zig in house for our development and while rust is preferred for complex projects, we prefer zig when we need: super portable code, code that will work with a static memory buffer or datastructre that are hard to use in rust (like self referencing).
We still have some chips that requires C because they have proprietary compiler, for those, we are very eager for the zig->C compiler that should be coming some day.
I mentioned that I hate Zig. I've tried it. I do not like it.
More power to you, though.
* I don't like how compile-time code can be mixed with runtime code. The idea is nice otherwise, but it confuses me.
* I spent a long time trying to understand Zig's async ([1]) and came away with more questions.
* I don't like the async model in general; I prefer structured concurrency. I think this is a mistake in Zig.
* The Zig community has put me off, though some of that is my own fault.
This is just a sample. I've written more other places, but I don't have access or can't remember where right now.
[1]: https://gavinhoward.com/2022/04/i-believe-zig-has-function-c...
I agree that async is peculiar, but I think it is still too early to decide if this works or not.
I feel bad that my brain doesn't work with it.
You have copy-paste operators like `inline for`, `inline while`, and `switch (x) { inline else => {} }` that focus on generating code given a value known at compile-time (think of it like running a C program to print out the code within a `for` loop then inlining that in your code) and are great when you need to unroll a loop or loop over the fields of a struct (e.g when printing it). If you find those difficult then try printing the code they inline to get a feel for how it changes your program or writing what they'd inline manually.
For the more template style `ArrayList(T)` style you can follow the logic yourself by writing the struct it would result in when you substitute the parameters (again a copy paste but with slightly more going on) and any more calls with the same parameters will always return the same struct back.
Branching at compile-time is like having a smarter `#ifdef` for the most part where you use `if`, `switch`, and other control-flow to decide which code should remain in the program as long as the condition is known at compile-time. This logic can be applied to the copy-paste operators too depending on the condition.
Async is very experimental and not complete so it's currently difficult for most. It can be thought of as a fancy way of creating a state machine with `.nextState()` being the `resume` keyword making it not a function but by having the compiler perform a little magic at compile-time it's possible to write code which doesn't care about this until you get to function pointers as then the difference starts to matter. If you want a C equivalent have a look at https://c9x.me/articles/gthreads/intro.html which covers it pretty well and it's almost what zig does for you with the `suspend` and `resume` keywords. `async` and `await` should be understood in terms of `suspend` and `resume` as they exist at a much higher level and can be a bit tricky. Another way to get a quick overview of what it's doing is to have a look at generators in python with `yield` and their equivalent with writing the state machine yourself as a class as it's equivalent.
If you feel like trying it again, don't be afraid to reach out on the zig room on matrix. I'll be happy to help with comptime and related things as it can be a bit difficult to grasp.
Oh and don't give up! The best parts about learning is the rewarding feeling when it finally clicks after all the hard work.
Thank you for the offer, but Zig really doesn't fit my brain. Nothing against Zig, either.
As opposed to the New Jersey school that produced C/Unix/etc.
Generally used along with the term "worse is better": https://en.wikipedia.org/wiki/Worse_is_better#Characteristic...
You can put guards on a razor blade, but it's still going to slice your finger.
There's a link in the post. It's at https://git.yzena.com/Yzena/Yc .