Exploring Polymorphism in C: Lessons from Linux and FFmpeg's Code Design (2019)
Polymorphism and object-oriented patterns can be built effectively in plain C using virtual function tables and carefully designed structs, as seen in large systems like the Linux kernel, FFmpeg, Dovecot, and classic game engines. Commenters compare these ad‑hoc C techniques with built‑in mechanisms in Go, Java, C++, Rust, and Objective‑C, highlighting trade‑offs between structural vs nominal typing, manual vs automatic dispatch, and performance vs ergonomics. The thread also touches on why many foundational libraries still choose C—portability, ABI stability, and interoperability—even as higher-level languages offer safer or more expressive abstractions.
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_ofmacro 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.
- Modern C++ (with RAII,
Control-flow and type-system details
- Discussion around C vs C++
if/whileinitializers:- 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.
- C++
- 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
/procentries 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.
- USB and complex
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).