DEV Community

Cover image for Why C is still the most important programming language
Dimension AI Technologies
Dimension AI Technologies

Posted on

Why C is still the most important programming language

Every programmer must learn it, even if they don't use it


Modern languages have replaced C in most application-level programming. Python dominates scripting and data science. Java and C# run enterprise systems. Go powers cloud infrastructure. Swift builds iOS applications. Rust is taking over systems programming.

Yet none of these languages has displaced C at the boundary.

When any of them needs to call a native library, talk to the operating system, or interoperate with code written in another language, it speaks C. Not because C is a good language – it has well-documented problems with memory safety and undefined behaviour – but because every platform defines the low-level rules for how compiled code communicates in terms of C.

Understanding why this is true, and what it means, is the reason every programmer should learn C.

ABI and FFI

When compiled code from one language calls compiled code from another, something must govern the mechanics of that call. Which CPU registers hold the arguments? Does the caller or the callee clean up the stack? How are structures laid out in memory? How are return values passed back?

These questions are answered by the Application Binary Interface, or ABI. The ABI is not defined by any programming language; it is defined by the platform. On Windows x64, Microsoft's calling convention applies. On Linux and macOS x64, the System V AMD64 ABI applies. On ARM64, the AArch64 ABI applies.

Every language that wants to call foreign code provides a Foreign Function Interface, or FFI. Python has ctypes and cffi. Rust has extern "C". Java has JNI. Zig has @cImport. The FFI handles language-level concerns: declaring signatures, marshalling strings, managing memory ownership. But when the call actually happens, the FFI must emit machine code that obeys the platform's ABI.

Here is the critical point: there is no single "C calling convention" that spans all platforms. Windows x64 differs from System V AMD64 differs from AArch64. But on every platform, the C compiler's calling convention becomes the platform's standard ABI. All other languages target that ABI when they need to interoperate.

ABI Stable Documented Universal on its platform
Platform C ABI
C++ ABI Varies by compiler Partially
Rust ABI Explicitly unstable
Swift ABI ✓ (since 2019) Apple platforms only

C++ ABIs differ between MSVC, GCC and Clang. Rust documents its ABI as unstable and reserves the right to change it. Swift's ABI is stable but was not designed for cross-language use.

The platform C ABI is the only binary interface that every compiler supports, every language runtime can target, and that has remained stable for decades. It works from assembly language with no runtime required.

The practical consequence: when you want to build a system spanning multiple languages – Rust for performance, Python for scripting, C# for business logic – every FFI involved will emit calls using that platform's C conventions. SQLite, OpenSSL, zlib, libgit2, LLVM, and thousands of other libraries expose C interfaces regardless of their implementation language, because the C ABI is the only interface all consumers share.

The operating system interface

The binary interface is not the only reason C remains central. The operating system itself is specified in C terms.

The Linux kernel is written in C. The Windows kernel is written in C. Darwin, the core of macOS and iOS, is written in C. FreeBSD, OpenBSD, and most embedded real-time operating systems are written in C.

More importantly, the portable operating system interface – POSIX – is specified as C function signatures and C types. File I/O, process management, signals, threads, memory mapping: all defined in terms that map directly to the platform's C ABI. This is not a philosophical choice about C's elegance; it is a practical choice about binary stability. C types are the lingua franca because the C ABI is the lingua franca.

Every language that wants to open a file, spawn a process, or allocate memory must ultimately call these interfaces. The call goes through the language's FFI, crosses into the platform's C ABI, and reaches the kernel.

The infrastructure that remains

An enormous amount of critical infrastructure is written in C and will remain written in C for the foreseeable future.

Linux, Git, curl, nginx, Apache, PostgreSQL, SQLite, OpenSSL, zlib, ffmpeg – foundational software that the modern internet depends on. These projects are actively maintained, and they are not being rewritten.

Someone must maintain this code, understand it when it breaks, fix the security vulnerabilities, diagnose the edge cases, and extend the functionality. That someone needs to read C fluently.

Even if you never contribute to these projects, you depend on them. Understanding the language they are written in helps you understand their behaviour, their limitations, and their failure modes.

The mental model

There is a secondary reason to learn C, less original but still valid: C forces you to confront the machine.

In C, memory is a flat array of bytes. Pointers are addresses. Allocation is explicit, and so is deallocation. There is no garbage collector, no hidden allocations behind convenient syntax. When you write malloc(100), you are responsible for those bytes.

A programmer who understands C understands what higher-level abstractions are hiding. They can reason about cache locality, memory layout, and the cost of indirection. When a Java program runs slowly or a Python script consumes unexpected memory, the programmer who learned C has a mental model for diagnosing the problem.

This is not the primary argument for learning C – the ABI argument is stronger and more specific – but it reinforces it.

What about Rust?

Rust and Zig are genuine competitors to C for new systems programming. They offer memory safety, better tooling, and modern language design.

In embedded and real-time domains – microcontrollers with 32 kilobytes of RAM, medical devices that cannot tolerate unpredictable latency, avionics and industrial control systems – C once had no competition. You needed a language that compiled to small, predictable, deterministic code with no garbage collector and no hidden runtime behaviour. Rust now offers this, with memory safety as well, and is a serious alternative.

More significantly, Rust has now been accepted into the Linux kernel. In December 2022, Linux 6.1 merged support for Rust as a second implementation language alongside C – and in December 2025, the kernel maintainers promoted Rust from experimental to a core part of the kernel. This is a genuinely epochal shift: for over 30 years, the Linux kernel was C and C alone. The decision to admit Rust, and then to make it a first-class citizen, reflects how seriously the kernel maintainers take its safety guarantees and how mature the language has become for systems work.

Even so, Rust in the kernel does not escape the C ABI. Rust kernel modules must interoperate with the vast existing C codebase through C-compatible interfaces. When a Rust library outside the kernel needs to be callable from Python, Go, C#, and Ruby, it exposes a C API. When Rust talks to the operating system, it uses C conventions. When Rust calls vendor SDKs, hardware abstraction layers, or existing libraries, it speaks C at the boundary.

Rust is not replacing C at the interface level; it is building safer implementations behind a C-compatible façade.

The only partial challengers

Two alternatives have attempted to displace the C ABI as the universal binary interface.

Criterion C ABI COM / WinRT WebAssembly
Cross-platform Windows only
Mature tooling Limited
Native performance Slower (5–30%)
No runtime required
OS API access Direct Direct Via host
Adoption in HPC / AI / scientific Dominant Rare Emerging
Stable since 1970s 1993 2017

COM, and its modern descendant WinRT, provides a binary object interface with versioning and cross-language support on Windows. It remains Windows-only and is rarely used in high-performance computing, scientific computing, or AI infrastructure.

WebAssembly defines a portable binary format with its own ABI and is the first serious challenger in forty years. WASM today is slower than native code, however, tooling remains immature, and every operating system API still speaks C. WebAssembly may grow into a genuine alternative, but it has not done so yet.

Conclusion

Fifty years of programming language research have produced remarkable advances in safety, expressiveness, and developer experience. Modern languages are genuinely better than C for almost every application-level task.

All of them, when they need to call outside themselves, target the C ABI.

This was not a deliberate choice. C was simply there first, and by the time anyone thought to standardise cross-language interoperability, C's conventions were already embedded in every operating system, system library, and driver.

The platform C ABI has become the binary equivalent of TCP/IP: a protocol everything else is built on, invisible until you look for it, impossible to escape once you do. The kernels are written in C, the OS interfaces are specified in C types, the critical infrastructure is maintained in C, and the ABI that connects everything is the C ABI.

You may never write a line of C. But if you write software that loads libraries, calls system APIs, embeds interpreters, or links against native code, you are programming against the C ABI whether you know it or not.

That is why every programmer should learn it.

Top comments (0)