Look Ma, hax0rz!*

* Of the grey-beard law-abiding kind
C Me After Class
I care a lot about ease of use for programming. To me, it’s an important consideration for both languages and APIs. Languages should aim to make it possible for anyone with good ideas and some determination to accomplish them. That’s why I was early to advocate for Python for most programming problems (the least-bad option), and am surprisingly okay with “Vibe Coding”.
But let’s face it, there are some problems where no easy tools are the right tool for the job. Python has always had challenges with performance, for the rare time that’s actually a major concern.
But more importantly, in my view, Python jumped the shark right around the time it added decorators (so 2004, before “the cloud” was out of diapers). Sure, Python might still be easier to use than other languages, but it feels a bit like Frankenstein’s monster: a sewn together monstrosity, slowly lurching down the street.
And while LLMs are getting better and better, they’re best at the things where there’s a vast sea of similar looking training data. I’m excited for an AI to take any UI work off my plate; it knows less than me, but is much better at cribbing Javascript quickly than I will ever be. But, when it comes to low-level code, they’re still not yet better than directly cut-and-pasting from the Stack Overflow questions the AIs have all memorized.
“Low-level” has long been a euphemism for “hard as ****”, no matter the language. C is the long reigning king of systems programming, and is absolutely an abomination. It’s disappointing how ubiquitous C is. But, it’s understandable:
- C++ blunts some risks, but levels up the complexity by piling on a ridiculous number of additional features, many of which were considered a good idea at the time, but now… aren’t.
- Rust is the modern version of Perl– a write-once language. Many people in the community seem to genuinely believe that, if you aren’t great at mathematics, you aren’t a real programmer (I’m not even kidding, I’ve heard that more than a few times).
- Zig might me an accessible successor someday, but still has a long way to go, and nowhere near as extensive an ecosystem as C, C++ and Rust. But it is getting better pretty quickly.
- There are a host of far less prominent options like D and Nim, but the lack of ecosystem makes them niche. Nim’s developer support is particularly abysmal, end to end.
Personally, writing Rust does make me feel good about myself– the fact that I can wrestle the borrow checker and win, makes me feel smart for a few minutes. I don’t begrudge the people who gush about Rust; it’s totally a good language (for line noise). It just feels too elitist for my sensibilities.
Let’s face it: I’m old, and the switching costs don’t seem to be worth the benefits. True grey-beard (bushy, not ironic) h4x0rs are fine with high-level languages for high-level tasks and prototypes, but for the low level stuff, the only acceptable alternative to C is assembly 💪.
I will be the first to admit that C is a horrible language that makes the world actively worse. If Python is Frankenstein’s monster, C is the mountain of raw body parts, nuts, bolts and tools, and trying to assemble anything with that mess might accidentally kill you before you really get started.
Still, no matter how abominable C is, it’s occasionally the right abomination for the job.
Ah, I’m imagining the impending flood of inevitable (and completely valid) complaints… how difficult it is to write; the safety and memory management challenges, the lack of basics… it doesn’t even have dictionaries!
But, did I mention that I’m old? I’ve spent a good portion of my adult life evolving my back of tricks to cope. Switching costs are high!!
When I write C code today, I have fast, thread-safe allocation, but with full garbage collection (and, supporting safety and debugging aids). I’ve also many other niceties behind that you’d expect in pretty much any language other than C, including performant data structures like dictionaries (both lock-free and locking), and a bunch of rich text tools.
I also write C with keyword parameters. This has long been a tool on my tool belt; but recently, I decided I’d had enough of the limitations of C’s macro system, and after a little bit of elbow grease, I now build functions to take keywords that mostly look like this:
int
h4x0r_find(h4x0r_string_t *s, h4x0r_string_t *sub, ...)
{
keywords {
int start = 0; // Default values if kw is not provided.
int end = -1;
}
// That's all you have to do in any function taking keywords.
// The rest is handled behind the scenes.
}
And passing keywords to those functions is pretty natural:
int result = h4x0r_find(s, sub, start : 5, end : -5);
Sharing the h4x
Dependency management in C is such a pain in the *** that most C developers only deal with them if absolutely necessary. Instead, they too develop their own bag of tricks (generally after a lot of pain and suffering, and occasional contemplation of embracing a different kind of void). It shouldn’t have to be that way.
Smart people who want to do innovative things should have a much lower barrier to entry.
The mission for h4x0r.org is to lower the barrier of entry for programming, by sharing our bag of tools, and other learnings (the anti-bag).
This isn’t specific just to systems programming— whatever the environment, we should be innovating to make programming easier, to help foster and facilitate other people innovating.
Personally, I’ll mostly stick to systems programming. But, over time, some of my friends and colleagues will join me in sharing their knowledge and tools. And, lucky for the world, only a few of my colleagues are willing to endure the self-torture of C the way that I am.
I’m going to start with a series of posts on memory management, because nobody deserves to suffer through C’s memory management (or Rust’s for that matter). With not too much work, we can get performant, thread-safe allocation that isn’t just easier to use than the default API, it can be fully garbage collected, under our control, without the downsides people somehow expect from GC (mostly due to suspect implementation choices of other GC environments).
After that, I’ll show how to build a bunch of other useful tools. Just as a small sample, this includes:
- Keyword parameters, per above. But we’ll have to build up to it, for instance with…
- Simple
once
functions for initialization that are far easier and more powerful than the verbose garbage in the pthreads API. - Dynamic, thread-safe lists, dictionaries, etc.
- A bunch of rich text and formatting capabilities.
- Believe it or not, an API to automatically marshal anything that can possibly be marshaled, with a single function call.
Sometimes we’ll build our own stuff, even if it seems like reinventing the wheel. In my view, it’s important learn how important things work. So yeah, we can build ourselves our own mutexes, even though it’ll be unremarkable. But, it will provide a good foundation for us, so we can go on and build better APIs for other types of concurrency primitives.
Besides, we systems programmers try to avoid dependencies (mainly because managing them is its own circle of hell).
I’ll aim to do one, maybe two posts every week, until I run out of things I’m excited to share, or until I get sick of writing. The first won’t happen soon– If I could magically freeze time, I’d have about 50 posts for you tomorrow.
Or, more likely, I’ll get sick of all the extra work quickly, and crawl back into my hole. If that happens, please leave me there!
Otherwise, welcome, and happy h4cking!
Lee T. H., h4x0r-in-residence @h4x0r.org @crashoverride.com