HN Debrief

To save C, we must save ABI (2022)

  • Programming
  • Infrastructure
  • Open Source

The post is a deep dive into a stubborn problem in C: once a type leaks into a public function signature or struct layout, that choice hardens into ABI and becomes painfully hard to change later. The author uses `intmax_t` as the case study and argues that C needs standard ways to offer old and new binary entry points side by side, so toolchains and libraries can evolve without instantly breaking every downstream binary.

If your software ships binary plugins, shared libraries, or certified long-lived systems, assume ABI constraints will dominate language evolution and upgrade plans. Treat public C-facing types as permanent, and isolate them behind narrower interfaces before you need to change them.

Discussion mood

Mostly skeptical but engaged. People accepted the ABI lock-in problem, yet felt the proposal aimed at the wrong layer, overstated what standard C can repair, and underestimated how much real-world breakage lives in third-party libraries, operating systems, and certification-heavy deployments.

Key insights

  1. 01

    Libc tricks do not fix library ecosystems

    Library ecosystems break long before libc does. If `intmax_t` or another unstable type appears in your own exported functions or public structs, every binary built against that header has frozen the old choice. Symbol aliasing can help one library ship parallel entry points, but it cannot magically upgrade all dependent libraries that already compiled the old layout into their ABI.

    Audit your public headers for width-dependent and layout-sensitive types now. If a type might need to change later, keep it out of exported structs and function signatures or wrap it behind opaque handles.

      Attribution:
    • pdw #1
    • aw1621107 #1
  2. 02

    ABI governance sits below the C standard

    Platform interoperability comes from ABI agreements tied to architectures, operating systems, and compiler families, not from ISO C itself. That framing changes the whole proposal. The standard can add language hooks, but the hard coordination still has to happen among toolchains, distros, and platform owners that decide what binaries must keep linking.

    Do not wait for a language standard to solve deployment compatibility. If you own a platform, SDK, or distribution, your ABI policy and compiler support matrix are the real levers.

      Attribution:
    • flohofwoe #1 #2
    • uecker #1 #2
  3. 03

    Y2038 and certification are the real deadline

    `time_t` drew more concern than `intmax_t` because it sits in software that may never be recompiled. In regulated and industrial systems, a rebuild is not just a CI job. It can trigger recertification, retesting, and operational risk. That makes ABI-safe time transitions a business problem as much as a language one.

    If you sell into industrial, medical, automotive, or other certified environments, inventory every persisted and exported time representation now. Plan migration paths that work even when fielded binaries cannot be rebuilt on demand.

      Attribution:
    • prussian #1
    • AnimalMuppet #1
  4. 04

    C feels low-level because the platform meets it halfway

    What makes C usable as a systems boundary is not only the language. It is the surrounding contract that parameters appear in known registers, memory can be addressed in flat chunks, and compilers expose escape hatches like intrinsics and inline assembly. That is why C lives in an awkward middle ground. It is higher level than assembly, but much of its practical power comes from platform assumptions outside the spec.

    When you compare systems languages, separate language semantics from toolchain and platform affordances. Many migration plans fail because they assume the replacement only needs syntax parity, not the same escape hatches and boundary contracts.

      Attribution:
    • titzer #1 #2
    • im3w1l #1
  5. 05

    Inline assembly is not replaceable by external calls

    Several comments knocked down the idea that assembly support is just a convenience outside the language. Inline assembly can bind directly to chosen registers, avoid call overhead, and expose hardware instructions or control operations before intrinsics exist. For embedded and high-performance code, moving that into a separately linked object changes both cost and capability.

    If your codebase depends on inline assembly, treat it as a first-class portability and maintenance risk. Count those sites before assuming another compiler or language can be a drop-in replacement.

      Attribution:
    • jonhohle #1
    • Dwedit #1
    • convolvatron #1 #2

Against the grain

  1. 01

    Unstable ABI is a feature for new languages

    Freezing an ABI early locks in mistakes before a language has learned what its core abstractions should be. From that angle, Rust and similar languages are doing the sensible thing by keeping their native ABI unstable and using C only at the boundary. That shifts pain onto interop, but avoids turning immature design choices into decades-long constraints.

    If you are designing a new language, runtime, or plugin model, resist pressure to promise stable native ABI too soon. Stabilize a narrow foreign-function boundary first and keep the internal calling convention movable.

      Attribution:
    • imtringued #1
  2. 02

    Whole-program builds make ABI mostly disappear

    In embedded work, ABI often matters less because systems are built and linked as one unit. That is a reminder that much of this problem is self-inflicted by shared libraries, plugins, and separately shipped binaries. If you control the full build, source compatibility often matters far more than binary compatibility.

    Where you own deployment end to end, prefer whole-program builds and static linking if they simplify upgrades. You can buy back a lot of design freedom by avoiding binary extension points you do not truly need.

      Attribution:
    • hdhcbdb #1
  3. 03

    C does not need saving to keep dominating

    Calls for C to die ran into the obvious reality that huge amounts of infrastructure, firmware, and vendor interfaces are still written in it. Even if better languages win new projects, installed C code and C-shaped boundaries will outlive the people arguing about them. The market can stop loving C without stopping its relevance.

    Plan for a long hybrid world. Even aggressive migrations should assume C interop remains a permanent requirement for critical dependencies and vendor stacks.

      Attribution:
    • Elder_Baker #1
    • icedchai #1

In plain english

ABI
Application Binary Interface, the low-level rules that let separately compiled binaries call each other, including calling conventions, type layouts, and symbol naming.
inline assembly
Assembly code written directly inside a higher-level language source file so the compiler can integrate it into generated machine code.
intmax_t
A C standard integer type meant to hold the largest signed integer type supported on a platform.
intrinsics
Compiler-provided functions or syntax that map directly to specific CPU instructions or low-level operations.
ISO C
The official C language standard published by the International Organization for Standardization.
libc
The standard C library implementation provided by an operating system or toolchain.
time_t
The C type used to represent calendar time, often tied to the Year 2038 compatibility problem on older systems.
Y2038-style transitions
Changes needed to move software and data formats away from older time representations that will fail in 2038.

Reference links

Standards proposals and committee papers

Background essays on C and ABI

Examples and side references from comments

  • Everything in C is undefined behavior
    Used in an argument about how easy it is to fall into undefined behavior when reinterpreting bytes in C.
  • xxHash source
    Suggested as a concrete C codebase to test claims about rewriting low-level performant code in other languages.