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_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).