inopinatus 2 days ago

This is an excellent pattern in C. The Dovecot mail server has many fine examples of the style as well e.g.

    struct dict dict_driver_ldap = {
        .name = "ldap",
        .v = {
            .init = ldap_dict_init,
            .deinit = ldap_dict_deinit,
            .wait = ldap_dict_wait,
            .lookup = ldap_dict_lookup,
            .lookup_async = ldap_dict_lookup_async,
            .switch_ioloop = ldap_dict_switch_ioloop,
        }
    };
defines the virtual function table for the LDAP module, and any other subsystem that looks things up via the abstract dict interface can consequently be configured to use the ldap service without concrete knowledge of it.

(those interested in a deeper dive might start at https://github.com/dovecot/core/blob/main/src/lib-dict/dict-...)

  • dividuum 2 days ago

    So does the good old Quake 2 rendering API. The game exported a bunch of functions to the renderer via refimport_t and the renderer in return provided functions via refexport_t. The only visible symbol in a rendering DLL is GetRefAPI_t: https://github.com/id-Software/Quake-2/blob/master/client/re...

    I remember being impressed by this approach, so I shamelessly copied it for my programming game: https://github.com/dividuum/infon/blob/master/renderer.h :)

    • dfox 2 days ago

      I somehow suspect that the reason why Quake2 does this lies in the legacy of Quake1 written in DJGPP. DJGPP supports dynamicaly loaded libraries (although the API is technically unsupported and internal-only), but does not have any kind of dynamic linker, thus passing around pair of such structs during library initialization is the only way to make that work.

    • jamesfinlayson a day ago

      Pretty sure Half-Life does something pretty similar - all functionality between the game and engine is done via function pointer structs.

  • jrmg 2 days ago

    Reminds me of Apple’s CoreFoundation.

social_quotient 2 days ago

I spend a ton of time in FFmpeg, and I’m still blown away by how it uses abstractions to stay modular—especially for a project that’s been around forever and still feels so relevant. Those filtergraphs pulling off polymorphism-like tricks in C? It’s such an elegant way to manage complex pipelines. e.g.

ffmpeg -i input.wav -filter_complex " [0:a]asplit=2[a1][a2]; [a1]lowpass=f=500[a1_low]; [a2]highpass=f=500[a2_high]; [a1_low]volume=0.5[a1_low_vol]; [a2_high]volume=1.5[a2_high_vol]; [a1_low_vol][a2_high_vol]amix=inputs=2[a_mixed]; [a_mixed]aecho=0.8:0.9:1000:0.3[a_reverb] " -map "[a_reverb]" output.wav

That said, keeping those interfaces clean and consistent as the codebase grows (and ages) takes some real dedication.

Also recently joined the mailing lists and it’s been awesome to get a step closer to the pulse of the project. I recommend if you want to casually get more exposure to the breadth of the project.

https://ffmpeg.org/mailman/listinfo

  • MuffinFlavored 2 days ago

    how similar are the C abstractions in ffmpeg and qemu given they were started by the same person?

    • variadix 2 days ago

      I haven’t worked with ffmpeg’s code, but I have worked with QEMU. QEMU has a lot of OOP (implemented in C obviously) that is supported by macros and GCC extensions. I definitely think it would have been better (and the code would be easier to work with) to use C++ rather than roll your own object model in C, but QEMU is quite old so it’s somewhat understandable. I say that as someone who mostly writes C and generally doesn’t like using C++.

      • shmerl 2 days ago

        What's the reason for ffmpeg to use C, also historic?

        • defrost 2 days ago

          Fabrice also wrote the Tiny C compiler, so very much his language of choice ..

          For those used to the language it was seen as "lighter" and easier to add OO like abstractions to your C usage than bog down in the weight and inconsistencies of (early) C++

          https://bellard.org/

          https://en.wikipedia.org/wiki/Fabrice_Bellard

          • lelanthran 2 days ago

            > weight and inconsistencies of (early) C++

            Since very little is ever removed from C++, all the inconsistencies in C++ are still there.

            • maccard 2 days ago

              Every language has inconsistencies, and C is not stranger to that. Much of c++’s baggage is due to C and you carry the same weight. That’s not to say that initialization isn’t broken in C++, but just like many features in many languages (off the top of my head in C - strcpy, sprintf, ctime are like hand grenades with the pin pre pulled for you) don’t use them. There’s a subset of C++17 that to me solves so many issues with C and C++ that it just makes sense to use. An example from a codebase I spend a lot of time in is

                  int val;
                  bool valueSet = getFoo(&val);
                  if (valueSet) {}
                  printf(“%d”, val); // oops
              
              The bug can be avoided entirely with C++

                  if (int val; getFoo(&val)) // if you control getFoo this could be a reference which makes the null check in getFoo a compile time check
                  {}
                  printf(“%d”, val); // this doesn’t compile.
              • ho_schi 2 days ago

                For C users. And C++ users:

                In C++ we can declare variable in the while or if statement:

                https://en.cppreference.com/w/cpp/language/while

                https://en.cppreference.com/w/cpp/language/if

                It's value is the value of the decision. This is not possible with C [1].

                Since C++17 the if condition can contain an initializer: Ctrl+F if statements with initializer

                https://en.cppreference.com/w/cpp/language/if

                Which sounds like the same? Now you can declare a variable and it value is not directly evaluated, you also can compare it in a condition. I think both are neat features of C++, without adding complexity.

                [1] Also not possible with Java.

              • ablob 2 days ago

                You could write this in C, no?

                  { int val; if (getFoo(&val)) {
                    ...
                  }}
                
                Both ways of expressing this are weird, but stating that this can't be achieved with C is dishonest in my opinion.
                • maccard 2 days ago

                  If I reformat this,

                      {
                          int val; 
                          if (getFoo(&val)) {
                      
                          }
                          printf("%d", val);
                      }
                  
                  The bug is still possible, as you've introduced an extra scope that doesn't exist in the C++ version.

                  Also, this was one example. There are plenty of other examples.

                • i_am_a_peasant 2 days ago

                  it's not that weird to explicitly limit the scope of certain variables that logically belong together.

                  But i agree the C++ if(init;cond) thing was new to me.

                  • maccard 21 hours ago

                    Like everything in C++, it has it's share of footguns

                        if (Foo* f = GetPtr(); f->HasValue()) {} // wrong; f can be null
                    vs

                        if (Foo* f = GetPtr(); f && f->HasValue()){}
                    
                    Is probably the biggest pitfall. Especially if you're used to this:

                        if (Foo* f = GetPtr())
                        {
                            f->DoTheThing(); // this is perfectly safe.
                        }
              • tialaramex 2 days ago

                Variables like "valueSet" scream out that the language lacks a Maybe type instead. One of the worst things about C++ is that it's content to basically not bother improving on the C type system.

                • maccard a day ago

                  C++ has optional, but I wanted to demonstrate that you could wrap a C API in a safer more ergonomic way.

                  If you rewrote it in a more modern way and changed the API

                      std::optional<int> getFoo();
                  
                      if (auto val = getFoo()) {}
                  
                  There are lots of improvements over C’s type system - std.array, span, view, hell even a _string_ class
                • pjmlp a day ago

                  I would vouch that C++ has plenty of improvements of C type system, even C++ARM already provided enough improvements that I never liked plain old C, other than that year spent learning C via Turbo C 2.0, before being given access to Turbo C++ 1.0 for MS-DOS in 1993.

                  The problem is all the folks that insist coding in C++ as if it was C, ignored all those C++ improvements over C.

                • rowanG077 2 days ago

                  C++ has a maybe type. It's called std::optional.

                  • uecker 2 days ago

                    Here is my experimental maybe type for C: https://godbolt.org/z/YxnsY7Ted

                    • tialaramex a day ago

                      Six divided by minus one is a "Division by zero" now? Where I come from that's minus six.

                      Good luck to WG14 (or maybe a faction within it?) as they seem to have decided to go make their own C++ competitor now, it's a weird time to do that, but everybody needs a hobby.

              • uecker 2 days ago

                This will be in C2Y and is already supported by GCC 15: https://godbolt.org/z/szb5bovxq

                • maccard a day ago

                  This is one example. Off the top of my head std.array vs "naked" C arrays, string vs const char*, and let's not forget RAII are all features that just make me never want to work with vanilla C ever again.

        • layer8 2 days ago

          C has less moving parts — it’s more difficult to define a subset of C++ that actually works across all platforms featuring a C++ compiler, not to mention of all the binary-incompatible versions of the C++ standard library that tend to exist — and C is supported on a wider variety of platforms. If you want to maximize portability, C is the way to go, and you run into much fewer problems.

        • emmelaich 2 days ago

          Much easier to link / load into other language binaries surely.

          • adastra22 2 days ago

            extern “C” works just fine.

            • p_l 2 days ago

              Only in certain limited cases, for example, can't have static class instances or anything else that could require calling before a call from "extern C" API.

              Also now you have to build enough of a C API to expose the features, extra annoying when you want the API to be fast so it better not involve extra level of indirections through marshalling (hello, KDE SMOKE)

              At some point you're either dealing with limited non-C++ API, or you might find yourself doing a lot of the work twice.

            • josefx 2 days ago

              Only if your entire API doesn't contain any C++.

              • pjmlp 2 days ago

                C is also C++, at least the C89 subset.

                • procaryote 2 days ago
                  • pjmlp 2 days ago

                    Do you usually post links without reading them?

                    "In the strict mathematical sense, C isn't a subset of C++. There are programs that are valid C but not valid C++ and even a few ways of writing code that has a different meaning in C and C++. However, C++ supports every programming technique supported by C. Every C program can be written in essentially the same way in C++ with the same run-time and space efficiency. It is not uncommon to be able to convert tens of thousands of lines of ANSI C to C-style C++ in a few hours. Thus, C++ is as much a superset of ANSI C as ANSI C is a superset of K&R C and much as ISO C++ is a superset of C++ as it existed in 1985.

                    Well written C tends to be legal C++ also. For example, every example in Kernighan & Ritchie: "The C Programming Language (2nd Edition)" is also a C++ program. "

                    • josefx 2 days ago

                      > For example, every example in Kernighan & Ritchie: "The C Programming Language (2nd Edition)" is also a C++ program. "

                      That is rather dated, they do things like explicitly cast the void* pointer returned by malloc, but point out in the appendix that ANSI C dropped the cast requirement for pointer conversions involving void, C++ does not allow implicit void conversions to this day.

                      • pjmlp 2 days ago

                        > Well written C tends to be legal C++ also

                        The "well written" remark is relevant.

                        Many style guides will consider implicit void conversions not well written C.

                        Naturally we are now on C23, and almost every C developer considers language extensions as being C, so whatever.

                        • josefx a day ago

                          > The "well written" remark is relevant.

                          So using casts that hid implicit int declarations for years is "well written"?

                          > Many style guides will consider implicit void conversions not well written C.

                          I could not find a "Many" guide, Linux Kernel and ffmpeg seem to advocate against pointless casts.

                          > Naturally we are now on C23,

                          So irrelevant to the creation time of ffmpeg and only applicable to intentionally non portable libraries.

                        • uecker 2 days ago

                          Well written idiomatic C is certainly not valid C++.

                          • pjmlp 2 days ago

                            Depends on the beholder, however it hardly matters on the days of C23, as mentioned.

                    • procaryote 18 hours ago

                      I don't see your point. The thing you quote explicitly says C isn't a subset of C++.

                      > Well written C tends to be legal C++ also

                      and python2 can be written to be compatible with python3, but neither is a subset of the other

                    • gjvc 2 days ago

                      Do you usually post links without reading them?

                      all the time -- I call it "crowd-sourcing intelligence" :-)

    • bonzini 2 days ago

      QEMU's abstractions were added when Fabrice was almost completely inactive already (starting in 2008 for some command line support).

cbarrick 2 days ago

For the record, this design pattern is called a virtual method table, or vtable.

I'm surprised that this article never mentioned the term.

C++ programmers will know this pattern from the `virtual` keyword.

  • rzzzt 2 days ago

    You can take it a step further:

    - instead of setting the same function pointers on structs over and over again, point to a shared (singleton) struct named "vtable" which keeps track of all function pointers for this "type" of structs

    - create a factory function that allocates memory for the struct, initializes fields ("vtable" included), let's call it a "constructor"

    - make sure all function signatures in the shared struct start with a pointer to the original struct as the first parameter, a good name for this argument would be "this"

    - encode parameter types in the function name to support overloading, e.g. "func1_int_int"

    - call functions in the form of "obj->vtable->func1_int_int(obj, param1, param2)"

    • starspangled 2 days ago

      > You can take it a step further:

      No, that is essentially what Linux does in this article (and by the looks of it also ffmpeg).

      struct file does not have a bunch of pointers to functions, it has a pointer to a struct file_operations, and that is set to a (usually / always?) const global struct defined by a filesystem.

      As you can see, the function types of the pointers in that file_operations struct take a struct file pointer as the first argument. This is not a hard and fast rule in Linux, arguments even to such ops structures are normally added as required not just-in-case (in part because ABI stability is not a high priority). Also the name is not mangled like that because it would be silly. But otherwise that's what these are, a "real" vtable.

      Surely this kind of thing came before C++ or the name vtable? The Unix V4 source code contains a pointers to functions (one in file name lookup code, even) (though not in a struct but passed as an argument). "Object oriented" languages and techniques must have first congealed out of existing practices with earlier languages, you would think.

    • drivebyhooting 2 days ago

      Is this satire? That’s almost exactly the C++ way.

      • p_l 2 days ago

        It's how you do it in many C++ implementations, but IIRC it's not actually mandated in any way unless you strive for GCC's IA-64 ABI compatibility (the effective standard on Linux for C++)

        C++'s vtables are also, in my experience, especially bad compared to Objective-C or COM ones (MSVC btw generates vtables specifically aligned for use with COM, IIRC). Mind you it's been 15 years since I touched that part of crazy.

        • pjmlp a day ago

          It is more the other way around, COM was designed to fit with how MSVC generates vtables.

          It is a simplification of OLE, and by the time the idea came up to use that approach, there were tons of OLE code since Windows 3.1.

          By the way it wasn't gone away, after how Longhorn went down, it became the main API delivery mechanism on Windows, sadly improving the tooling has never been a pritority other than half-finished attempts.

      • relistan 2 days ago

        It’s not satire, it’s how you do full OO in plain C.

        • actionfromafar 2 days ago

          It's overloaded - it is satire, but also, it isn't.

          • rzzzt 2 days ago

            Exactly. Thank you for your time, I will and won't be here all week!

  • chrsw 2 days ago

    I've noticed many large C projects resort to these sorts of OOP-like patterns to manage the complexity of the design and size of the code base. But I'm not aware of any one standard way of doing this in C. It seems C++ standardized a lot of these concepts, or C++ developers adopted standard patterns somehow.

    • discreteevent 2 days ago

      > I've noticed many large C projects resort to these sorts of OOP-like patterns to manage the complexity of the design and size of the code base

      The Power of Interoperability: Why Objects Are Inevitable

      https://www.cs.cmu.edu/~aldrich/papers/objects-essay.pdf

      • pjmlp a day ago

        Objects regardless of what shape they take, are basically an evolution of modules that can be passed around as values, instead of having a single instance of them.

        That is why they are here to stay, and even all mainstream FP and LP languages offer features that provide similar capabilities, even if they get other names for the same thing.

        It is like saying an artifact is useless, only because it get named differently in English and Chinese.

    • jcelerier 2 days ago

      and C++ also supports optimizing them, especially when you use `final` keyword and LTO which is able to devirtualize at the scale of a whole program.

      • i_am_a_peasant 2 days ago

        Interesting, in Rust those optimizations are more implicit since there's no "final" keyword when you use dynamic dispatch via trait objects. + you also got LTO.

        I wonder if there are many cases where C++ will devirtualize and Rust won't.

        But then again Rust devs are more likely to use static dispatch via generics if performance is critical.

        • pornel a day ago

          In Rust objects can dynamically go in and out of having virtual dispatch. The vtable is only in the pointer to the object, so you can add or remove it. Take a non-virtual object, lend it temporarily as a dynamically dispatched object, and then go back to using it directly as a concrete type, without reallocating anything.

          That's pretty powerful:

          • any type can be used in dynamic contexts, e.g. implement Debug print for an int or a Point, without paying cost of a vtable for each instance.

          • you don't rely on, and don't fight, devirtualization. You can decide to selectively use dynamic dispatch in some places to reduce code size, without committing to it everywhere.

          • you can write your own trait with your own virtual methods for a foreign type, and the type doesn't have to opt-in to this.

        • tialaramex 2 days ago

          > But then again Rust devs are more likely to use static dispatch via generics if performance is critical.

          Put another way, in C++ the dynamic dispatch is implicit, so you might write code which (read literally) has dynamic dispatch but the optimizer will devirtualize it. However in Rust dynamic dispatch is explicit, so, you just would not write the dynamic dispatch - it's not really relevant whether an optimizer would "fix" that if you went out of your way to get it wrong. It's an idiomatic difference I'd say.

        • jcelerier 21 hours ago

          > But then again Rust devs are more likely to use static dispatch via generics if performance is critical.

          I'm not sure I follow - pretty much 99% of usage of C++ in the last, like, 20 years has been around making sure that you get static dispatch with polymorphism through templates. It's exceedingly uncommon to see the `virtual` keyword unless you have, say, some DLL-based run-time plug-in system going on.

          • i_am_a_peasant 16 hours ago

            yeah true, I'm really liking pornel's reply though on rust objects going between static and dynamic dispatch.

KerrAvon 2 days ago

> The interface type in golang is much more powerful than Java’s similar construct because its definition is totally disconnected from the implementation and vice versa. We could even make each codec a ReadWriter and use it all around.

This paragraph completely derailed me — I’m not familiar with golang, but `interface` in Java is like `@protocol` in Objective-C — it defines an interface without an implementation for the class to implement, decoupling it entirely from the implementation. Seems to be exactly the same thing?

  • mananaysiempre 2 days ago

    The difference between Go and Java is that in Go a type need not declare its adherence to an interface up front—any type that has methods of appropriate names and signatures is considered to implement the interface, even if its designers were not aware of the interface’s existence. (This involves a small bit of dynamism in the runtime; easily cached, though, as the set of methods of a given type and the set of all interfaces are both fixed by the time the program runs.) Whether that’s a good thing depends on your design sensibilities (ETA: nominal vs structural).

    • jeroenhd 2 days ago

      For those wishing Java had a similar feature, there's Manifold: https://github.com/manifold-systems/manifold/tree/master/man...

      Manifold is a very interesting project that adds a lot of useful features to Java (operator overloading, extension classes, and a whole bunch more). I don't know if it's smart to use it in production code because you basically go from writing Java to writing Manifold, but I still think it's a fun project to experiment with.

      • sitkack 2 days ago

        http://manifold.systems/

        > Manifold is a Java compiler plugin, its features include Metaprogramming, Properties, Extension Methods, Operator Overloading, Templates, a Preprocessor, and more.

        Neat tool. It is like having a programmable compiler built into your language.

    • JavierFlores09 2 days ago

      The funny thing about Java is that while its design is to be entirely nominally typed, the way it is implemented in the JVM is compatible with structural typing, but there are artificial limitations set to follow the intended design (though of course, if one were to disable these limitations then modeled type safety goes out of the window as Java was simply not designed to be used that way). One community which takes advantage of this fact is the Minecraft modding space, as it is the basis[1] of how modding platforms like Fabric work.

      1: https://github.com/SpongePowered/Mixin/wiki/Introduction-to-...

    • owlstuffing 2 days ago

      >The difference between Go and Java is that in Go a type need not declare its adherence to an interface up front.

      Go can't declare adherence up front, and in my view that’s a problem. Most of the time, explicitly stating your intent is best, for both humans reading the code and tools analyzing it. That said, structural typing has its moments, like when you need type-safe bridging without extra boilerplate.

      • duskwuff 2 days ago

        You can assert that your type implements an interface at compile time, though, e.g.

            var _ AssertedInterface = &MyType{}
      • relistan 2 days ago

        One of the main uses for interfaces in Go is defining the contact for _your_ dependencies. Rather than saying your function takes a socket, if you only ever call Write(), you can take a Writer, or define another interface that is only the set of functions you need. This is far more powerful than declaring that your type implements an interface up front. It allows for things like e.g. multiple image libraries to implement your interface without knowing it, enabling your project to use them interchangeably. And as another commenter said, you can have the compiler verify your compliance with an interface with a simple (though admittedly odd looking) declaration.

        • williamdclt 2 days ago

          > It allows for things like e.g. multiple image libraries to implement your interface without knowing it

          That virtually never happens. Seriously, what would be the odds? It’s so much more usual to purposefully implement an interface (eg a small wrapper the writer thingy that has the expected interface) than to use something that happens to fit the expected interface by pure chance.

          It’s not a structural vs nominal problem but other, typescript is structural but has the implements keyword so that the interface compliance is checked at declaration, not at the point of use. You don’t have to use it and it will work just like Go, but I found that in 99% of cases it’s what I want: the whole point of me writing this class is because I need an interface implementation, might as well enforce it at this point.

    • williamdclt 2 days ago

      I don’t agree it’s a structural VS nominal difference. Typescript is structural, but it does have the “implements” keyword.

      Which makes a million times more sense to me, because realistically when do you ever have a structure that usefully implements an interface without being aware of it?? The common use-case is to implement an existing interface (in which case might as well enforce adherence to the interface at declaration point), not to plug an implementation into an unrelated functionality that happens to expect the right interface.

      • cognisent 2 days ago

        TypeScript doesn't require a class to use it, though, because it's structurally typed. All that "implements Foo" in this example does is make sure that you get a type error on the definition of "One" if it doesn't have the members of "Foo".

        If "Two" didn't have a "name: string" member, then the error would be on the call to "test".

            interface Foo {
                name: string
            }
        
            class One implements Foo {
                constructor(public name: string) {}
            }
        
            class Two {
                constructor(public name: string) {}
            }
        
            function test(thing: Foo): void {
                //...
            }
        
            test(new One('joe'));
            test(new Two('jane'));
    • kazinator 2 days ago

      GNU C++ once had this feature; it was called Signatures. It was removed, though.

      A signature declaration resembled an abstract base class. The target class did not have to inherit the signature: just have functions with matching names and types.

      The user of the class could cast a pointer to an instance of the class to a pointer to a compatible signature. Code not knowing anything about the class could indirectly call all the functions through the signature pointer.

      • pjmlp a day ago

        Nowadays you can do that with concepts.

        • kazinator 13 hours ago

          I don't see how concepts can emulate signatures to the full extent that the target object can be manipulated as if it conformed to an abstract base, without any wrapper object being required to handle it.

          Without signatures, we have to use some kind of delegating shim which takes the virtual function calls, and calls the real object. It could be a smart pointer.

          With signatures, we don't use smart pointers, just "pointer to signature" pointers. However, I suspect those pointers had to be fat! Because, surely, to delegate the signature function calls to the correct functions in the target object class, we need some vtable-like entity. The signatures feature must generate such a vtable-like table for every combination of signature and target class. But target object has no space reserved in it for that table pointer. The obvious solution is a two-word pointer which holds a pointer to the object, and a pointer to the signature dispatch table specific to the signature type and target object's class.

          If we can use concepts to do this, with a smart pointer that ends up being two words (e.g. pointer to its own vtable, and a pointer to the target object), we have broken even in that regard.

    • billfruit 2 days ago

      So golang supports 'duck typing'?

      • theLiminator 2 days ago

        I think in a static context, it's generally referred to as structural typing, but yeah.

  • juwjfoobar 2 days ago

    interfaces in Go are structural. Interfaces in Java are nominal and require immediate declaration of intent to implement at type definition.

    • psychoslave 2 days ago

      Shouldn’t this be named phenomenal rather than structural? In both cases there is a structure assumed, but one is implicitly inferred while the other one is explicitly required.

      • layer8 2 days ago

        The difference is that in Go, an interface is assumed to match if the method signatures match. In other words, the match is done on the type structure of the interface, hence the “structural” designation. Nominal typing, on the other hand, considers that interfaces tend to be associated with important semantic requirements in addition to the type signature, and that mere type-structure matching doesn’t at all guarantee a semantic match. For that reason, the semantics are implicitly bound to the declared name of the interface, and the way for an implementation to claim conformance to those semantics is to explicitly bind itself to that name.

      • relistan 2 days ago

        I think you’re making a joke, but in Go you get both. You can have the compiler enforce that you implement an interface with a simple declaration. Most people do.

        • psychoslave 2 days ago

          No intended joke in that case, but it’s nice to have the feedback it might seen as if it was.

          I don’t know Go to be frank, just had a very shallow look at it once because of an interview, and apart big names behind it, it didn’t shine in any obvious way — but that’s also maybe aligned with the "boring" tech label it seems associated with (that is, in positive manner for those who praise it).

sitkack 2 days ago

I'd say at 20kloc of C, https://www.lua.org/ gets you as far up the Object Oriented tower as you want.

  • quietbritishjim 2 days ago

    The article is about using OO techniques directly in C code. Lua is implemented in C but it's an entirely separate language. Does its implementation use OO techniques as part of its C source code? If not, then it's not really relevant.

    • sitkack 2 days ago

      I don't see how a distinction here is anything but semantically arbitrary.

      Transitively, it most definitely uses OO techniques. Furthermore, by having such a clean C ffi (in both directions) it allows for the weaving of the Lua based OO techniques back into C code.

favorited a day ago

> for instance, Linux handles network socket, special files (like /proc/cpuinfo) or even USB devices as files. This is a powerful idea that can make easy to write or use programs for linux since we can rely in a set of well known operations from this abstraction called file.

Benno Rice gave a fantastic talk a few years ago called "What UNIX Cost Us," which he starts off by showing how to write some USB device code in macOS, Windows, and Linux. It only takes a few minutes to demonstrate how pretending that everything is a file can be a pretty poor abstraction, and result in far more confusing code, which is why everyone ends up using libusb instead of sysfs.

https://www.youtube.com/watch?v=9-IWMbJXoLM#t=134s

  • DarkUranium 18 hours ago

    The same is visible in having to parse a bunch of Linux's more complex of the /proc entries, vs. simply using syscalls in (say) FreeBSD.

    "Everything is a file" is not a bad abstraction for some things. It feels like Linux went the route of a golden hammer here.

    • favorited 13 hours ago

      That's the gist of his whole talk – that doing things "the UNIX way" (which can be defined to various degrees of specificity) has been cargo culted, and that we should reexamine whether solutions that were pragmatic 50+ years ago is still the best we can do.

      The specific reason I mentioned it was because his initial example was about how much more ceremony and boilerplate is needed when you need to pretend that USB interfaces are actually magic files and directories.

glouwbug 2 days ago

    *int (*encode)(*int);
Why not compile your snippets? Heads up to the author.
loph 2 days ago

My recollection (which could be rusty, it has been >30 years) is that the Motif API, coded in C, implemented a kind of polymorphism.

jdefr89 2 days ago

What is with the incorrect function declarations? I see:

int (func)().

Maybe you meant: int * (*func)(void)?

Don't mean to be pedantic. Just wanted to point it out so you can fix it.

pjmlp 2 days ago

The language is called Go.

Other than that, yeah doing by hand what C++ and Objective-C do automatically.

  • programmarchy a day ago

    For one, ffmpeg is 9 years older than Go. Plus, when dealing with video files a garbage collected language probably isn't going to cut it. C++ and Obj-C also feel overkill for ffmpeg.

    • pjmlp a day ago

      Apparently someone has not read the article, otherwise you would have had understood my point about Go.

      Secondly, Apple and Microsoft, do just fine with Objective-C and C++ for their video codecs, without having to manually implement OOP in C.

      • programmarchy 11 hours ago

        CoreVideo and CoreAudio are both implemented in C on Apple systems. There are higher level APIs like AVFoundation implemented in Obj-C/Swift, but the codecs themselves are written in C. Even the mid-level frameworks like AudioToolbox and VideoToolbox are written in C. I’m not as familiar with Microsoft but imagine it’s similar.

        Also the article doesn’t actually mention OOP. You can use polymorphism without fully buying into OOP (like Go does).

        The great thing about C is its interoperability, which is why it’s the go to language for things like codecs, device drivers, kernel modules, etc.

high_na_euv 2 days ago

Who would have thought that OOP could be useful!

  • vkazanov 2 days ago

    This is not oop but polymorphism that is useful. And various forms of polymorphism are used in all kinds of programming paradigms.

    Also, this is a nice way to get the damn banana without getting lost in the jungle.

    • jcelerier 2 days ago

      polymorphism used that way is 100% the most traditional OOP possible ever

      • vkazanov 2 days ago

        Yes, a similar approach to polymorhpism is supertypical for popular OOP-focused languages, along with other concepts.

    • flykespice a day ago

      isnt polymorphism inherently a subset of oop?

      • vkazanov a day ago

        No, all popular functional languages have one flavour of polymorphism or the other.

        Or, in math speak, Oop means polymorphism but polymorphism doesn't mean oop.