Essential insights from Hacker News discussions

Object-oriented design patterns in C and kernel development

This Hacker News discussion explores the nuances of object-oriented programming (OOP) and its potential implementation in C, contrasting it with data abstraction. The core of the debate revolves around how to define and achieve OOP principles, with many users highlighting the effectiveness and historical context of C-based approaches.

The Definition and Scope of Object-Oriented Programming

A central theme is the varying definitions of OOP and whether C's function pointer patterns truly constitute OOP. Some argue that OOP requires specific compiler-enforced contracts, whereas C's approach is more akin to data abstraction.

  • ryao asserts that the Linux kernel's use of function pointers within structures is "data abstraction," not OOP, due to the lack of compiler-enforced contracts. They state, "My point is that this pattern is not object oriented programming." They further elaborate, "Object oriented programming implies certain contracts that the compiler enforces that are not enforced with data abstraction."
  • f1shy notes the fluidity of OOP definitions: "To be fair, OOP is not 100% absolutely perfectly defined. Strustrup swears C++ is OOP, Alan Key, at least at some point laughed at C++, and people using CLOS have yet another definition."
  • naasking suggests a more inclusive view: "If you think of 'is' as a whether there's a homomorphism, then it makes sense to say that it is OOP, but it can qualify as being something else too, ie. it's not an exclusionary relationaship."
  • ryao reiterates a key distinction: "If you think of the differences as being OOP implies contracts with the compiler and data abstraction does not (beyond a simple structure saying where the members are in memory), it becomes easier to see the two as different things."
  • ryao also points to a fundamental difference concerning unimplemented functions: "OOP cannot be a formalization of what predated it because the predating patterns support things that OOP explicitly disallows, like instantiation with unimplemented functions."

C as a Foundation for Object-Oriented Patterns

Many participants highlight how C's fundamental features, particularly function pointers within structs, can be used to implement patterns that are core to OOP, even if they predate formal OOP definitions. This allows for dynamic dispatch and polymorphism.

  • ryao introduces the core concept: "The article describes how the Linux kernel, despite being written in C, embraces object-oriented principles by using function pointers in structures to achieve polymorphism."
  • trws acknowledges the C approach and compares it to modern OOP: "I largely agree, and use these patterns in C, but you’re neglecting the usual approach of having a default or stub implementation in the base for classic OOP. There’s also the option of using interfaces in more modern OOP or concept-style languages where you can cast to an interface type to only require the subset of the API you actually need to call. Go is a good example of this, in fact doing the lookup at runtime from effectively a table of function pointers like this."
  • mistrial9 connects these patterns to compiler design: "The concept of abstract data type is a real idea in the days of compiler design. You might as well say 'compiler design predates object oriented programming'. The technique described in the lead is used to implement object-oriented programming structures, just as it says."
  • ryao counters that data abstraction originated in Lisp, also predating OOP.
  • pjmlp offers a counterpoint on the origins of data abstraction, citing "AN ALGORITHMIC THEORY OF LANGUAGE" from 1962, where similar concepts were known as "plexes."
  • pakl shares an example of a lightweight OOP system built on C, co2: "A few years ago Peterpaul developed a lightweight object-oriented system on top of C that was really pleasant to use[0]. No need to pass in the object explicitly, etc."
  • davikr questions why similar patterns haven't been integrated into new C versions, noting "Clearly, there is a significant demand for - lots of people reimplementing the same (similar) set of patterns."
  • 1718627440 extols C's virtues: "What I really like about C is that it supports these sophisticated concepts without having explicit support for them. It just naturally emerges from the core concepts. This is what makes it feel like it just doesn't restrict the programmer much."

The Role of Explicit vs. Implicit this and Runtime Dispatch

A significant portion of the discussion focuses on the this pointer and the trade-offs between explicit (C-style) and implicit (C++/Java-style) handling, as well as dynamic message dispatch.

  • pavlov describes the "original OOP way" in Smalltalk and Objective-C: "In Smalltalk and Objective-C, you just check at runtime whether an object instance responds to a message. This is the original OOP way. It's sad that OOP was corrupted by the excessively class-centric C++ and Java design patterns."
  • mettamage inquires about Objective-C's dynamic dispatch capabilities.
  • pjmlp confirms Objective-C's dynamic dispatch and links it to NeXTSTEP's PDO systems.
  • chunkyguy defends Objective-C's performance: "Don't know about other programming languages but with Objective-C due to IMP caching the performance is close to C++ vtable."
  • loeg criticizes the C VFS pattern's explicitness: "I think the author is talking about this: object->ops->start(object) Where not only is it explicit, but you need to specify the object twice (once to resolve the Vtable, and a second time to pass the object to the stateless C method implementation)."
  • 1718627440 defends the explicitness of passing this in C: "I personally don't like implicit this. You are very much passing a this instance around, as opposed to a class method. Also explicit this eliminates the problem, that you don't know if the variable is an instance variable or a global/from somewhere else."
  • spacechild1 suggests naming conventions solve the this ambiguity: "People typically use some kind of naming convention for their member variables, e.g. mFoo, m_Foo, m_foo, foo_, etc., so that's not an issue. I find foo_ much more concise than this->foo."
  • ryao shares a similar sentiment preferring explicit this: "Not allowing a variable to implicitly refer to a member variable makes it much easier to find. If it is not declared in the function and there is no implicit dereferencing of a this pointer, the variable is global. If the variable name is commonly used and it is a member variable, it is a nightmare to hunt for the correct declaration in the codebase with cscope."
  • ryao clarifies their stance on explicit this: "Good point. I had misunderstood the previous comment as suggesting that this be passed to the member function as an explicit argument, rather than requiring dereferences of this be explicit. The latter makes far more sense and I agree it makes reasoning about things much easier."
  • MontyCarloHall expresses a strong preference against implicit this: "Agreed, one of the biggest design mistakes in the OOP syntax of C++ (and Java, for that matter) was not making this mandatory when referring to instance members."
  • Gibbon1 calls implicit this "magic": "The implicit this sounds to me like magic. Magic! Ask how do I do this, well see it's magic. It just happens. Something went wrong? That's also magic. After 40 years I hate magic."
  • Galanwe argues for the benefits of implicit this: "I don't quite agree, especially because the implicit this not only saves you from explicitly typing it, but also because by having actual methods you don't need to add the struct suffix to every function."
  • 1718627440 suggests that syntactic sugar can be problematic: "Whenever you invent syntactic sugar you need to make some usage blessed and some usage impossible/needing to fallback to the old way without syntactic sugar."

Data Abstraction vs. OOP: Flexibility and Baggage

The discussion frequently contrasts the flexibility of C's data abstraction with the perceived "baggage" of full OOP implementations, especially in languages like C++ and Java. Performance considerations and the complexity of features like inheritance, templates, and virtual tables are also debated.

  • ryao advocates for data abstraction: "Data abstraction is a really powerful technique and honestly, object oriented programming rarely does anything that makes me want it over data abstraction. The only thing that I have seen object oriented programming do better in practice than data abstraction is marketing."
  • munchler distinguishes data abstraction from "full object-orientation": "Note that this is using interfaces (i.e. vtables, records of function pointers), not full object-orientation. Other OO features, like classes and inheritance, have much more baggage, and are often not worth the associated pain."
  • nphardon explains a practical use case for C in performance-critical applications: "We use C in parts of the engine because we need to manage memory at a very fine level to meet performance demands on our tool. Other parts of the tool use C++ because they decided the tradeoff benefited in the other direction, re memory access / management / ease of use."
  • 1718627440 argues that C's lack of strict formalism allows for greater creativity and understanding of complexity: "C makes it obvious were you use that dynamism and where you don't. Syntactic sugar doesn't really make that much of a difference and also restricts more creative uses."
  • 1718627440 also defends C's approach to features like templates and code generation: "For the basics there is a separate tool in the language: the Preprocessor... if you want more, you are free to choose your tool. If you want a macro language, there is e.g. M4."
  • munchler points out that mutable state differentiates classes from simple "scoped variables."
  • 1718627440 suggests that C's inheritance is simply struct composition: "struct Subclass { struct Baseclass base; }; That's not really that complicated."
  • ryao uses filesystem code as an example of the valid use of calling methods on different object types: "You are wrong about those invocations being invalid. Such patterns happen in filesystem code fairly often. The best example off the top of my head is: error = old_dir->i_op->rename(rd->new_mnt_idmap, old_dir, old_dentry, new_dir, new_dentry, flags);"
  • ryao clarifies their stance on casting and inheritance in C: "If you must think of it in OOP terms, imagine that your superclass is an abstract class, with no implemented members, except you can instantiate a child class that is also abstract, and you will never do any inheritance on the so called child class."
  • nphardon highlights another initialization pattern in C: "Another cool thing about this approach is you can have the arguments to your object init be a pointer to a structure of args. Then down the line you can add features to your object without having to change all the calls to init your object throughout the code base."
  • accelbred suggests using inline wrappers for vtable functions to achieve cleaner syntax.