Why I Still Reach for C in 2025
More and more these days, I find myself using C as opposed to other “more modern” languages, like Rust or Python. Sometimes when people ask me what languages I write code in, they usually expect to hear “Python” and give me quizzical looks when I say that I use one of the oldest (but still very popular!) programming languages around.
My Personal Ethos
For as long as I can remember, in my academic and professional life, I’ve valued the following ideals:
- Developing a deep understanding of all levels of a subject
- Simplicity: finding beauty in the elaborately straightforward
- Efficiency: doing the least work in the shortest amount of time, while still fully accomplishing the task at the highest level of quality
- Robustness: being capable of working independently with a minimal amount of resources
What do I do with C?
Most of my programming involves writing scientific software: numerical analysis programs that follow relatively straightforward sequences of operations, though those operations themselves might be complex. For example, I’ve written a few high-performance codes for calculating the magnetic fields of electromagnets. The workflow is pretty straightforward! You basically load a bunch of data into memory, and perform the same calculation with a combination of many input and output points. However, when you couple it all together into a library or a command line program, there’s a good number of moving parts.
Most of my work does not involve the web or lots of user interaction. I also don’t often need to worry about security, since my programs are used locally with minimal permissions and don’t transmit data over a network.
My History with Programming
When I was young, my dad, a software engineer, lent me his Python book and told me all about this ‘cool new language’ he had been learning for work. Version 2.0 had just been released! From an early age, I had been fascinated with all sorts of technology, including computers, space, robotics, airplanes - you name it.
The basic concepts weren’t all that crazy to me, though a lot of “what would I do with this?” was over my head. I learned about variables, functions, and objects, and had early aspirations of being a game developer. I loved the combination of physics and world-building. However, I was a kid and had all sorts of other things I was interested in as well, so I never got around to putting in a lot of the practice and deep work that it takes to make games.
In college, I took an elective course on MATLAB and used it in many other courses. This was the language of choice for me during undergrad and grad school, and for the first few years of full-time work, as well. It had an easy to use syntax and it was purpose-written for what I was doing - engineering calculations.
During undergrad, I had also taken a course or two that involved basic robotics using Arduino microcontrollers. The Arduino language is sort of like a superset of C++, and while I didn’t fully understand everything about it, I appreciated the utility even though it was not popular among my peers (I very much appreciated their concerns as well). In grad school, again I took a hands-on course on robotics and ended up writing a ton of code combining an Arduino microcontroller and a PC-based graphics display written in Processing (sort of like Java). I also took a course on object-oriented programming in C++. This was my first ‘hardcore’ academic introduction to software engineering, and I loved it! I’ve barely written any C++ code since, for reasons I’ll get into later.
A year and change later, I finally got around to trying a language I had been hearing a lot about, called Julia. I wrote a 2D simulation for landing rockets in that language, and while my attempts were somewhat clumsy (I was mastering controls engineering topics I had just learned in grad school while simultaneously attempting to pick up an entirely new language), I loved the idea that there was an open-source language that was also purpose-built for engineering and scientific software development workflows. Move aside, MATLAB!
Throughout all this time, Python had been ever-growing in popularity and become my go-to language at work. Most engineers I work with, if they knew any programming at all, would use Python. In this way, I learned about network effects and how they could propel sometimes clumsy solutions into immense popularity.
More recently, I’ve been learning Rust, too. The software engineers at my company really like it, so I started writing a computational fracture mechanics code in Rust to learn the language. At some point earlier this year, after getting a good taste of the Cargo ecosystem and a lot of the nice bells and whistles that the combination of cargo, rustc, and rust-analyzer gave me, I declared that I would “write all new programs in Rust!”. This was short-lived, for reasons I’ll also get into later.
In total, I’ve written nontrivial programs in the following 8 languages:
- C
- C++ (and Arduino, and Processing - all lumped together)
- Python
- Julia
- Rust
- MATLAB
- Fortran
- Hare
I also did a bit of web development in HTML+CSS, though that doesn’t count here.
It was the discovery of Hare, sourcehut, and Drew Devault’s blog that made me want to pivot back to a C-focused workflow.
The Insanity of Modern Webdev Stacks Radicalized Me
In my professional work, I perform a lot of engineering calculations using finite element programs. In the modern day, these finite element programs function somewhat like videogames. They abstract a lot of the math and some of the physics away from the user, separating understanding from reality. I learned how to do this kind of work just when the paradigm shifted: finite element “analysis” became rebranded to ’engineering simulation’, and many more engineers had access to them during their day-to-day duties.
Since I learned how to do this from the greybeards, I had an old-school mindset regarding computational efficiency and a bit of scarcity mindset as to what I asked the computer to do. I became hardwired to simplify problems as much as possible, sometimes even at the cost of wasting more engineering hours ($$$) to save compute time ($). It was in my nature to prioritize this kind of efficiency, because in doing so, I became more skilled, understood more levels of what it is that I was actually doing, and produced very clean end results.
Earlier this year, I wanted to write a desktop GUI application and had found a number of toolsets that might match what I needed. One of these was Tauri, a GUI framework? library? convoluted mess? that I still have yet to figure out how to use to write a program.
In Tauri, you write JS for your front-end, Rust for your backend, and then bundle it together into something that makes use of the webview on your PC to render the program. Sounds nice on paper; you don’t ship an entire browser with your program like you do with Electron, and you use each language for what it’s good at.
I cannot overstate this enough, I hated how this paradigm worked in practice. There’s more than half a dozen JS frameworks to choose from (what even is a framework? after 5 years of trying to figure this out I’ve given up), and now we’re working in 2 languages rather than just one. I can’t just write code and send it to the compiler, I have to install and use something like npm which might itself require me to install homebrew or another package manager? There were many, many new things for me to understand how to do, and what I was trying to accomplish felt pretty straightforward to me.
Anyways, this was a radicalizing experience. All I wanted to do was have some buttons to click on and generate a few 2D scientific plots in a standalone application on my PC. It’s 2025. I cannot accept that this is a difficult proposition.
In looking for an alternative to GitHub, I found sourcehut. I did some due diligence, and discovered the founder’s blog, after which I found that he’s been working on a language called Hare. In the Hare FAQ’s (yes this is a rabbit-hole, and pun intended), I found this gem of an insight:
How does Hare compare to other languages?
Compared to other languages in Hare’s niche…
[…]Rust: Hare is much, much simpler than Rust. Hare lacks generics, traits, macros, and many more features commonly used in Rust. Hare also provides fewer memory safety guarantees than Rust. Moreover, Hare is culturally distinct from Rust, for example we have no package manager and encourage less code reuse as a shared value. However, Hare has a comprehensive language specification, a much faster toolchain and build times, and Hare programmers tend to identify simpler and more maintainable solutions to the same problems. If you’re happily using Rust, Hare is probably not for you; if you are unhappily using Rust, consider trying Hare.
At the time I read this, I had just spent two months (on and off, in a few hours of my free time) struggling with a design for my fracture mechanics program in Rust. I was gradually starting to realize that it was I was spending more time struggling with managing the complexity of the Rust language, rather than writing an efficient, maintainable program. It wasn’t that my program had a complex design, it was that the design of the Rust language was vastly complicating my efforts.
Also in the Hare FAQ’s:
Hare is designed to be a small, simple, and stable programming language that punches above its weight on systems programming tasks. Hare is a language that most of its experienced users should be able to understand in its entirety: it’s feasible that one person can read and understand the entire compiler, for example. This also becomes easier given that Hare is self-contained – it does not depend on libc. Hare is also designed to be stable for the long-term: when we’re done with Hare 1.0, the language syntax and semantics will freeze and it can be depended upon indefinitely.
and
Why qbe instead of LLVM?
Hare’s backend, unlike many new languages making use of LLVM, is based on qbe. qbe clocks in at a mere ~15,000 lines of portable C99 code; LLVM is counted in the tens of millions. The ability for a single programmer to fully understand the Hare toolchain is one of our goals, and this rules out LLVM.
This does not come without trade-offs: qbe generates slower code compared to LLVM, with the performance ranging from 25% to 75% the runtime performance of comparable LLVM-generated code, depending on the workload. Often this can be mitigated by hand-writing assembly in hot sections; for example Hare’s standard library utilizes Intel’s AES-NI x86-64 instructions when available, achieving substantially better performance than an AES implementation compiled with either qbe or LLVM.
Wait, isn’t Rust built on LLVM?!
I loved the idea that a language existed, which I could fully understand and fully understand the compiler, too. After dabbling a bit, I realized that while very neat, Hare doesn’t meet my current needs because:
- I unfortunately have to write code that can be compiled to run on Windows platforms (not supported by Hare)
- I’m writing high-performance numerical code and I need a fast compiler
So, I decided the next best option would be C.
What does C do for me?
Don’t get me wrong, I don’t love writing C. Sometimes, it’s a downright pain (anyone care to teach me how to really use a debugger?). The upsides are worth it to me, though:
- I can write really fast code in C, and this matters a lot to me
- Writing programs in C forces me to keep them simple, which makes them faster to develop and easier for me to understand when I’m far along in the dev cycle
- Of any language I’ve used, C follows closest to what the computer hardware is actually doing (allocating memory, moving objects around in cache, performing calculations in the CPU) and I love that it fosters a closer link between the code and the computer.
- I don’t have a package manager, so when I use external dependencies, I have to more fully understand them, and it puts a lot of friction into this process. I’ve found that I don’t often need everything another library provides me, I just need one or two functions, and so implementing them falls into my ideal of “learning all levels of a problem. Obligatory Hare FAQ reference:
Why doesn’t Hare have generics?
Omitting generics (and similar features) in Hare is a deliberate design choice which simplifies the language considerably and is more aligned with its design roots in C.
Our semi-official casual explanation of this choice is that many programs have, at most, one or two really important data structures central to their design (and responsible for their bottlenecks), and because these data structures are important and central to the design, it’s wise for you to implement these yourself so that you can (1) understand them and (2) adapt them to your specific use-case. Optimizing every other data structure that your program makes use of, but which is not bottlenecking your performance, is a premature optimization.
Nail in the coffin!
In short: C teaches me a lot more about how to write better code and how the PC works than any other language I’ve worked with.
Throwing Some Friendly Jabs at Rust
All that being said, I really do think Rust has some great ideas. I love that if you successfully compile a decently-written Rust program, chances are very high that the compiler caught most of the nastiest bugs that might be lurking in your code. Also, Cargo provides a lot of nice ergonomics, and I love how writing documentation and tests works in Rust. For managing dependencies (gross) (mostly kidding), the module system and crates.io do a great job.
I also don’t truly think that Rust is overly complex. I think it is appropriately complex for writing software that is robust, efficient, and should last a long time. In the same way that C is often the right tool for me, Rust is often the right tool for eliminating bugs and hazards of writing large programs with many people involved.
If I was working on software that had more security-critical implications, or on larger codebases, or if I worked on larger software teams, I’d want to use Rust instead of C.
Just Getting Stuff Done
C isn’t the best choice for every (or even most) applications. However, for my purposes, it’s often a really good one. I often use C in tandem with Julia and Python, because it’s relatively trivial to write a wrapper around a C program in those languages and then use it in a more dynamic and interactive manner. The latest version of Julia (1.12.x) is excellent, and I look forward to using that as the ‘front-end’ to most of my iterative development processes going forward.