In 2026 we have soo many options to choose from - C++, Rust, Zig, Oden. Etc… But C - the language that was created in 1972 is still my choice for most of my projects. Why is that?

There are some traits that are unique to C that you cannot find in any of the modern languages. And everytime I try these languages it always inevitably come me back to C for these traits.

In this post, I am going to discuss what these unique traits are and why you may have to consider C for you’re next project.

Predictable

C is literally a macro language for assembly.

When I look at every line of C code, inside my head I can directly see how the assembly looks. In other words output generated by C compiler is very predictable. I mean take a look at this code:

// ...
BeginDrawing();

  DrawText("Hello, World!", 100, 80, 20, 0xFF0000FF);

EndDrawing();
// ...

And this is how it roughly (not a copypasta of disassembly) looks like in x86-64:

call BeginDrawing
lea rax, msg
mov esi, 100
mov edx, 80
mov ecx, 20
mov r8d, 0xFF0000FF
call DrawText
call EndDrawing

As a programmer our end goal is to somehow load the right instructions into the memory that get job done. Handtyping assembly is waste of time and benefits are very small at the cost of unmaintainable code.

One way of looking at C is that it is literally a macro language for assembly that is cross-platform (yes C is cross-platform, some people have hard time believing it).

Knowing what is the underlying instructions is important to unravel how truly you’re program works and how it can be improved.

Raw

C only gives you what you need to eat. Nothing more, nothing less.

One of the beauty (and annoying part at the same time) of C has no magic! You want function overloading? Wrap it around macro 1

static void _throw_expect_but_got_ex(parser_t *p, const token_t *tokens,
                                     size_t tokens_size,
                                     token_t current_token) {
    // ...
}

#define _throw_expect_but_got(p, t1, t2) \
  _throw_expect_but_got_ex(p, &(token_t){t1}, 1, t2);

Want to generate code on compile time? Unlucky for you C macros are limited and you have to do it manually as part of you’re build process.

Here is example of command line argument parsing code generation using m4 1. In our build system we have to generate flags.c from flags.c.m4.

I’m sorry in advance if you’re not a Linux nerd and don’t understand the next example (even though the most ugly parts are behind that include at the top). But that’s how real-life code generation looks like.

include(`flags.m4')dnl
FLAG_DEF(run, r, Compile and run the output, {
  state.action = CA_RUN;
})dnl
FLAG_DEF(quiet, q, Do not show verbose output, {
  state.quiet_mode = true;
})dnl
dnl
dnl ...
dnl
/* Code generated from flags.c.m4 */

#include "flags.h"
#include "utils.h"

void flags_usage(char *prog_name, FILE *f) {
  fprintf(f, "Usage: %s <input> [options]\n\n", prog_name);
  fprintf(f, "Options:\n");
  fprintf(f, "    --help, -h        Show this help message\n");
  FLAG_USAGE_GEN
}

void flags_parse(char *prog_name, char **argv) {
  char *arg;
  while ((arg = SHIFT(&argv)) != NULL) {
    if (arg[0] == '-') {
      if (strcmp(arg, "--help") == 0 || strcmp(arg, "-h") == 0) {
        flags_usage(prog_name, stdout);
        exit(0);
      }
      FLAG_COND_GEN
      else {
        log_err("Unknown flag \"%s\"", arg);
        flags_usage(prog_name, stderr);
        exit(1);
      }
    } else {
      fileinfo_t info = file_info(arg);
      if (!sv_eq_ignorecase(info.ext, sv_from_cstr(".cd"))) {
        log_err("Invalid file type \"*" SV_FMT "\".", SV_ARG(info.ext));
        log_err("Only *.cd files are supported.");
        exit(1);
      }
      da_append(&state.inputs, info);
    }
  }
}

But the point here is C only gives you what you need to eat. Nothing more, nothing less. And if you want to do something extra, it has to be done explicitly - which is part of the codebase.

This avoids bugs where the mistake is hidden behind some magic that is not visible in you’re code - which are honestly the worst kind of bugs.

Next question might be - how is this simple when you need a C compiler and m4 interpretor to build the project? There are lot of moving parts.

Yes, you are correct!

Powerful

My herd mind can’t accept this but logically it make sense!

You can write anything in C! Literally all you’re coreutils are written in C. GNU m4 is written in C. GNU Autotools is written in C. Heck even C compilers are written in C.

So why not write the build system and scripts in C and make them share code with the main project? Why treat build system and the project as separate entities?

Libraries like nob.h by Alexey Kutepov - which is a STB-style library that is specifically designed with this philosophy in mind 2:

#define NOB_IMPLEMENTATION
#include "nob.h"

#define BUILD_FOLDER "build/"
#define SRC_FOLDER   "src/"

int main(int argc, char **argv) {
    NOB_GO_REBUILD_URSELF(argc, argv);

    Nob_Cmd cmd = {0};

#if !defined(_MSC_VER)
    nob_cmd_append(&cmd, "cc", "-Wall", "-Wextra", "-o", BUILD_FOLDER"hello", SRC_FOLDER"hello.c");
#else
    nob_cmd_append(&cmd, "cl", "-I.", "/Fe:"BUILD_FOLDER"hello", SRC_FOLDER"hello.c");
#endif // _MSC_VER

    if (!nob_cmd_run(&cmd)) return 1;

    return 0;
}
$ cc -o build build.c   # Iniital bootstrap.
$ ./build               # Build the project. It'll also automagically re-build
                        # itself if you update the build script.

I believe in 2026, self-hosted build scripts this should be the norm. We can already see languages like Zig following this philosophy3.

I know it hard to comprehend - even my herd mind can’t accept this but logically it make sense! Following this philosophy leads to my final unique trait about C…

Replicable

It’s not about the language at all! Its about the machine instruction.

C as a language is very simple to parse and re-create - meaning software that is written in C are immortal (kind of).

Languages are just human readable interpretation of machine instructions. Even if in a hypothetical future all the C compilers in this world are gone (which will never happen as all the useful codes are written in C) you can very easily write a C compiler and port all C source codes to this futuristic machine instructions.

Modern language comes. Modern language goes. They bring alot to the table don’t get me wrong. But they are not easy to replicable.

We should never fixate ourselves to a specific programming language. It’s not about the language at all! Its about the machine instruction.

As a cherry on top, if you following the previous philosophy - all you need is a C compiler!

Conclusion

C is the best choice for you’re next project in my opinion. Maybe I’m wrong or maybe I’m right.

But how do you define right or wrong anyways?

- Siddeshwar

Footnotes:

  1. Code examples taken from my compiler 2

  2. Example taken from nob.h examples

  3. Zig Build System