header image
under construction image

This website is still under construction.

Blog

The Problem With C Programming Language Killers

There are new applications written in C every day, and people use it in their daily job to build critical applications. This is very surprising since the C programming language is 5 decades old and there are many “modern programming languages” that are specifically designed to replace it. This leaves us with a question: “Why do these people still choose to write C?”.

Are they trying to prove some sort of elitism? What is the reason behind choosing C over all these supposedly “better” languages?

C stands for Computer and those who understand C code are just better than most programmers that’s not even a question. The problem that I’m seeing with all these supposedly “C Killers” is the fact that they all missing the whole point of the C programming language which is the simplicity.

The reason I chose to write in C is that I just don’t want to deal with abstractions that I don’t even know how they works underneath or compiler yelling at me “Nooo you can’t do this” or “That’s unsafe”. I just want to write some code and the compilers job is to just translates my code into machine-code.

Although this simplicity and design choices are mostly because of the limitation that they had back then; But it turns out that this simplicity and almost “one-to-one” translation to machine-code is precisely the reason C is the lingua franca of the programming and people still use it for building applications.

Adding all this additional syntactical sugar and abstractions is harmful, because you are adding additional layer for the programmers to learn, and also you are making the compiler more complicated than it should be.

Another problem with the modern compilers is the Optimization (with some exceptions). It’s your duty as a programmer to write optimized code instead of relying on the compiler to optimize it for you. Compiler should just translate the code into machine-code and further optimization should be done using intrinsic’s not the compiler itself (And in some cases inline assembly1).

By doing this you are also making the compiler simpler, more maintainable, and presumably faster. You also allow the programmer to just go to the definition of a procedure to see how it is actually implemented without the need to read specs or the source-code of the compiler.

What Can We Learn From Modern Languages? #

Like all things in this world, C is not perfect, and we all know it. But if we are going to keep using it might as well make it better. We must not fear to explore territory of other compilers and understanding their design decisions. Let’s explore some of the ideas that I think could improve C.

Compiler Errors #

Probably One of the best features of the modern programming languages is their compiler errors. They have these nice compiler errors which exactly tells you what’s wrong with your code. Or when you are doing something wrong they would tell you how to do it in a correct way.

The thing is that there is no “correct way” to do things in C, and you can just make things work. This makes it harder to actually check what you are doing is wrong without adding extra restrictions. But I would like to have some warnings that stop me from shooting myself in the foot and I should be able to disable those warnings when I’m doing something a bit unconventional.

Meta-Programming & Macros #

One of the greatest futures of the Rust compiler (rustc) is probably the procedural macros and just macros in general. Some people tend to hate macros, but I believe this is mostly because the compilers they used had a terrible macro-system. Like the C pre-processor!

Macros are truly magical and the amount of things you can do with procedural macros are limitless and since the code is generated at the compile time you have all sort of information that you don’t really have at the runtime of the program.

For example take a look at the serde crate in Rust and the way serde uses the procedural macros to generate methods for serializing the deserializing data. Most of the time unless you are building some custom protocol, you just want to serialize or deserialize data and move on with your life. Doing this manually (using json-c for example) when the compiler has all the information that it needs to do it for you is really tedious and cumbrous.

Meta-Programming and Code-Generation are not something new. I don’t know the exact history for it, but you can clearly see tools like xxd have this flags to export data as a header file. Even go has a feature for generating code at compile time.

I believe having a good macro system can help a lot in the process of writing code, and It can be used to implement compile-time features that otherwise wouldn’t be possible without changing the compiler itself.

Universal Build System #

Building C code is a mess, CMake is just unnecessarily complicated and tools like Ninja and Make are good enough but not convenient to be used in large projects.2 Hence we need for tools to generate the build-script to build the project.

I believe build-scripts written in the programming language itself is the most portable and probably the best way to do it. If you write the build script in the language itself, all you need is a compiler to build your program. You wouldn’t need additional tools to actually compile your program and learn a new language for just building your program.

We can see modern languages like Rust (build.rs), Zig (build.zig) and Jai (first.jai I believe) are also adopting this pattern instead of using a traditional build tools and shell-scripts.3

Wrapping Things Up #

I want to emphasize that programming languages are merely tools for creating software (art). And it doesn’t matter how you create art, what matters the most is the art itself. Nobody cares if you are using tabs or spaces, pure functions or classes, or you use this programming language or that. At the end of the day what matters is that the features are implemented, and the software is running as expected.

By bike shedding and adding all this additional abstractions on top you are just making things more difficult for yourself and new programmers. Maybe instead of building additional tools, abstractions and standards4 we just have to step back and fix the tools that actually get things done.


  1. Using inline assembly is not recommended for most applications, and it can lower the performance. This is because compilers tend to rely on certain registers and reuse them or whatever to produce optimal code and by using inline assembly you are breaking this flow of the compiler. ↩︎

  2. One could argue that make is Turing-Complete and you can basically do anything with it, which is just dumb in my opinion. ↩︎

  3. build.rs build scripts are not actually the tool building the Rust program, rather they help cargo (The Rust package manager) to link with third-party libraries or just provide additional options to the compiler during the compilation process. ↩︎

  4. xkcd: Standards ↩︎