Exploring Polymorphism in C: Lessons from Linux and FFmpeg's Code Design (2019)

Structural vs nominal typing and interfaces

  • Major subthread compares Go, Java, TypeScript, and C++:
    • Go interfaces are structural: any type with matching methods satisfies an interface without declaring it. You can still assert conformance with a compile-time trick.
    • Java interfaces are nominal: types must explicitly declare implements, tying semantics to names rather than just structure.
    • Some praise Go’s flexibility for defining small dependency-specific interfaces; others argue implicit conformance is rarely useful and explicit implements (as in TypeScript) is usually preferable.
    • Historical parallels: GNU C++ “Signatures” and C++ concepts. Debate over whether concepts can fully replace runtime, dynamically dispatched “signature” objects without shims.
    • Note that JVM implementation could support structural typing, and some ecosystems (e.g., modding) exploit this via mixin-like tools.

Polymorphism and OOP patterns in C

  • Core pattern discussed is virtual method tables (vtables):
    • Use structs of function pointers, often via a shared, const “vtable” struct and per-object pointer to that table.
    • Linux file_operations, FFmpeg filters, mail servers (Dovecot dicts), game engines (Quake 2, Half-Life), COM, and CoreFoundation-like APIs are cited as examples.
    • The container_of macro from the Linux kernel is mentioned as an essential tool for such patterns.
  • Some call this “doing C++ by hand”; others stress it’s just polymorphism, which exists beyond OOP.
  • Lua is suggested as a way to get OO “on top of C”, but others note the article is about patterns in C itself, not embedding another language.

Language choice and complexity: C vs C++/Go/Rust

  • Reasons given for sticking with C in large projects like FFmpeg/QEMU:
    • Historical origin, author preference, portability, simpler ABI and interop story, fewer “moving parts” than C++.
    • View that early C++ was heavy and inconsistent; some still prefer adding OO-style patterns to C over using modern C++.
  • Counterpoints:
    • Modern C++ (with RAII, std::optional, arrays, strings, templates) can remove many C pitfalls and support devirtualization and whole-program optimization.
    • Disagreement over “C is a subset of C++” and how much idiomatic C is actually valid C++.
    • Go is mentioned mostly to correct naming and to contrast garbage collection / polymorphism model; Rust’s trait objects vs generics are briefly compared to C++ virtuals vs templates.

Control-flow and type-system details

  • Discussion around C vs C++ if/while initializers:
    • C++ if (init; cond) vs C’s need for extra scopes; some see this as a real safety win, others note you can approximate it with braces in C.
    • Mention of new C2x/C2Y features that move closer to C++’s behavior.
  • Broader dispute over whether C++ meaningfully improves C’s type system or just adds complexity, and whether Maybe/optional types should replace ad-hoc flags.

UNIX “everything is a file” critique

  • Pushback on the article’s praise of the “everything is a file” abstraction:
    • USB and complex /proc entries on Linux cited as cases where forcing file semantics leads to awkward, boilerplate-heavy code.
    • Argues that syscalls or libraries (e.g., libusb) are clearer, and that UNIX design patterns are sometimes cargo-culted beyond their useful domain.

Miscellaneous

  • Several comments note incorrect or uncompiled C snippets in the article (e.g., malformed function pointer declarations).
  • Question about FFmpeg support for SVE is raised; no clear answer in the thread (unclear).