Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Plain C API design, the real world Kobayashi Maru test (nibblestew.blogspot.com)
143 points by jmillikin on April 16, 2023 | hide | past | favorite | 76 comments


I've done a few APIs similar in spirit to this one (and one similar in functionality, too), and I've found using a blend of a few of the mentioned methods to be pretty effective.

I start with the basics and implement everything as explicitly (read: type safe) as possible:

    struct bob_params {...}
    struct line_params {...}

    pdf_page_cmd_bob(bob_params *params) { ... }
    pdf_page_cmd_line(line_params *params) { ... }
Then, when that works and I want to add patterns, which are a superset, I'd add something that is literally a superset of those functions.

    enum pattern_type {
      patterntype_Bob,
      patterntype_Line,
    }

     struct pattern {
       pattern_type type;
       union {
         bob_params Bob;
         line_params Line;
       }
     }

     pdf_pattern_cmd(pattern *pattern) {
       switch (pattern->type) {
         case patterntype_Bob: /* do bob drawing a bunch of times, or whatever */ 
       }
     }

     // The pattern struct has a lot of different names (sum type, discriminated union, algebraic datatype, tagged union .. probably more), but the idea is the 'type' tag tells you which one of the union values to use.
I've tried everything and, as far as I can tell, this gets you the best of all worlds. It's pretty much as type-safe as things get in C. It's extremely flexible; you just mix the fundamentals together as you like when adding higher-order functions. It's fast; the compiler can see exactly what's going on, so you pay minimal runtime cost.

Yes, it's a bunch of typing to get the functions all spelled out, but it's really not that bad considering how easy/obvious the code actually is.

I use this pattern so much I actually wrote a little metaprogramming language that is capable of generating a lot of the boilerplate for you. Link in my bio, if anyone's interested in looking at it.


If you’re designing a stable API for external users, you might want to lock down your API even more by forward declaring the struct types as opaque in the public header file and only defining the struct members in a private library header file. This prevents users from messing with your library’s private state and allows you to change implementation details later without breaking binary compatibility. The disadvantage is that users can’t control how the structs are allocated or embed them in their own structs.

  /* foo.h */
  struct foo_object;

  struct foo_object* foo_create(int flags, …);

  /* foo.c */
  #include “foo.h”

  struct foo_object {
    int flags,
    …
  };


>> The disadvantage is that users can’t control how the structs are allocated

Yeah, that usually kills that plan for me. I also try to avoid heap allocation wherever possible (which is usually pretty easy), and that solution ends up with an everything-is-heap-allocated nest of pointers, which I really try to avoid.

I typically do a request-response thing where the user code asks how big something is, then they do the allocation, and pass the allocation into the library. Windows does this a lot and, while Microsoft products are generally a giant garbage fire these days, I think this is one pattern they really got right.


Having spent a considerable time in the Mines of Microsoft, I see what you're getting at but argue that unless you're shipping proprietary, closed source code, this is just unnecessary.

It's much simpler to just mark fields as private with some convention. If callers want to muck with your struct, they will. Opaque types is a big stick, I rarely reach for it. Occasionally it's useful, but I struggle to recall when a simpler, less hostile approach was worse.


Generally I agree, but have one big disagreement with those statements.

>> It's much simpler to just mark fields as private with some convention. If callers want to muck with your struct, they will.

Agreed, and people mucking with my struct is usually the least of my worries. If a user wants to do that, I say go to town.

The "ask for the size first" approach has one major advantage compared to shipping the full struct:

1) You can hide all the random-ass internal structs you use, which can greatly reduce the amount of header code you have to ship. I like shipping a single header w/ a static lib and this can be a huge win for that use case. Note that I don't want to hide the internal stuff because I think users are incompetent and will ruin both our days by doing something dumb. I want to hide noise that they would literally never care about, and don't need definitions for.

That all said, packaging an API is pretty tricky to get right and depends a lot on the actual functionality. Maybe we work on different subject matter, and therefore have different views.


Fair, though I guess I no longer live in a world where shipping pre-built libraries is a thing. Builds are either monolithic or open-source and vendored to the point where none of this matters.

Ultimately I want to be able to step through the full source, and from my perspective it's easiest if the library could just be inserted into my source tree regardless of whether it actually is or not.

I'm not saying I particular like when libraries grow to support a custom allocator either, but I suppose there's some inevitable law that holds, preceding the lisp interpreter being added. At that point the Microsoft approach seems strictly more laborious with less clear value, at least to me.


> I use this pattern so much I actually wrote a little metaprogramming language that is capable of generating a lot of the boilerplate for you. Link in my bio, if anyone's interested in looking at it.

Your language [0] is an interesting idea. I've done a lot of similar stuff (not at work, I'm very rarely asked to write C code professionally, but I like mucking around with it in my personal time). My own approach to C metaprogramming involves obtuse preprocessor hacks (e.g [1]) and shell scripts that use grep/awk/sed to find macro invocations and spit out new header files as a result. Maybe your way is better.

A word of warning though – do you realise your name for your language is an anti-gay slur in several dialects of English? You might end up unintentionally upsetting some people with it.

[0] https://github.com/scallyw4g/poof

[1] https://github.com/pfultz2/Cloak/wiki/C-Preprocessor-tricks,...


>> My own approach to C metaprogramming involves obtuse preprocessor hacks

I considered doing this, albeit briefly, but decided against it for two reasons:

1) I hate the preprocessor. 2) I found a few projects that did that already.

Ultimately, writing a bespoke C parser and scripting language that sits on top of it was kind of an experimental accident. I just started doing it, and it's ended up working out so well that I've kept pushing on it to see where the natural conclusion is.

>> do you realise your name for your language is an anti-gay slur in several dialects of English?

I actually didn't realize that, thanks for pointing that out. I'll be changing the name.


> A word of warning though – do you realise your name for your language is an anti-gay slur in several dialects of English?

I'd say 'poof' is worse. Jesse's a real name in some parts of the world.

https://www.urbandictionary.com/define.php?term=Poof


I don’t understand your comment. I’m wondering if maybe you misunderstood me as talking about Jesse’s name as opposed to his language’s name.


>anti-gay slur in several dialects of English

So is puff. Am I to give up actual puff the magic dragon and sugar puffs? Being English I like the occasional puff on a fag aswell, there's a double entendre for every one there.

And then there's the pouffe that I rest my legs on...


> So is puff. Am I to give up actual puff the magic dragon and sugar puffs? Being English I like the occasional puff on a fag aswell, there's a double entendre for every one there.

I never told him he had to change it. I was simply checking if he was aware it was an anti-gay slur (in some countries), in case he wasn't – and indeed he said he wasn't. Now he is aware that it is, it is his decision how to respond. He's said he's going to change the name. If he'd decided the opposite, to keep the name – it wouldn't be my place to criticise his decision. If someone else felt it was theirs, that would be between him and them, not me.

And, personally, I've never heard ''puff'' been used as an anti-gay slur, but I've heard ''poof'' been used that way many times. But sure, when you tell me it has been, I believe you.

If someone insisted that ''Puff the Magic Dragon's'' name be changed because ''puff'' is a slur, I'd think that was silly. But there's a big difference between a 50 year old song, which exists in many people's childhood memories (it does in mine), and a pre-alpha open source project which very few have heard of, where if there'd ever be a good time to change its name, it would be now.


It's almost exclusively pronounced puff where I am.

The problem is that the author doesn't really know the cultural nuances, simply saying it's a possible issue isn't helpful because they don't know the relative usages of certain words. I wouldn't be particularly worried about using poof because there already homophones that are innocent so it isn't like fuck. Plus at this point Im not even sure if it's derogatory or just slang for gay.

But anyway, I'm firmly in the intent camp when it comes to words. If the word as used is obviously not meant to offend, offense shouldn't be taken. The world's got enough problems without extracting insults from open source projects.

Fwiw puff the magic dragon to me is synonymous with smoking weed.


> It's almost exclusively pronounced puff where I am.

At high school in Sydney, Australia in the 1990s, I remember hearing poof as an anti-gay slur a lot - to the point that if I hear the word, that's the first thing that pops into my head. And poof is a clipping of poofter, which I heard a lot too, and the two words get mentally merged together. Whereas puff, to me that's a completely separate and unrelated word, which is pronounced differently, I never thought of connecting the two. From what you are saying, in some dialects in England, the two words are pronounced the same – didn't know that before

> Plus at this point Im not even sure if it's derogatory or just slang for gay.

It is derogatory, but it also has "re-claiming" uses. Few will be offended by a gay person using it, but someone who isn't gay calling gay people that is going to offend a lot of people. Similar to the situation with Black people calling themselves the n-word versus non-Black people doing it

In Australia, poof and poofter are associated with the so-called poofter-bashing, an epidemic of violent assaults and murders of gay men in the 1970s thru 1990s, in which the police were complicit. I think that history makes poof/poofter more offensive in Australian English than they are in British English

https://www.nytimes.com/2017/01/30/world/australia/australia...

> Fwiw puff the magic dragon to me is synonymous with smoking weed.

When I was a little kid, my mother used to play "Puff the Magic Dragon" (the Peter, Paul and Mary song) all the time. That's what it makes me think of, my early childhood. I know some people linked it to weed, but that's never been a prominent link in my head-and it is a link which the band themselves have always vehemently denied. Anyway, who really wants to mentally associate weed with their parents’ taste in music?


Funnily enough that's the most common pattern I see in my personal C code (for example, my little lisp interpreter - https://github.com/lelanthran/csl/blob/master/src/parser/ato...) but I still recommend using the `Generic` keyword in C.

For the next time I do a pattern like this, I'll be using `Generic` keyword to make the dispatch a compile-time match, not check at runtime.


I think you may be disappointed. Every single time I've tried to use `_Generic`, I've found that it's more trouble than it's worth. They seem to have made it be useful for a very narrow case -- tgmath.h -- and not bothered to make it general enough to be applicable to a wide variety of things that you might like to use it with.


Can you elaborate slightly on how you're planning on using _Generic to turn runtime dispatches into compile time ones? I'm not quite putting together how you can do that.


The example from the wikipedia page for C11 (https://en.wikipedia.org/wiki/C11_(C_standard_revision)#Chan...) is compile time determination of the function to call:

     #define cbrt(x) _Generic((x), long double: cbrtl, \
                               default: cbrt, \
                               float: cbrtf)(x)
In code you'll write `cbrt(foo)` and the correct function will be called for the type of foo. As I understand it, the `_Generic` selection is performed at compile time.


Recommend `__attribute__((overloadable))` instead of _Generic. The former opts into C++ style name mangling, which is a bit of a mess in C, but interacts well with `static` as forwarding wrappers in a header. The latter is a ridiculous mess invented by the C committee.


The latter does not require any name mangling. Any function that is called, was first created by the programmer. You might not like it, but it certainly fits any C code that wants to interface with other languages.

Furthermore, I could only find this feature in the clang-compiler and references to it outside of the online-docs in gcc.


can we just agree that PDF sucks? Sometime's the API must operate on a spec that is unruly to begin with. I like this approach. It's about the best you can get when dealing with this without completely reinventing the storage format to support the API.


0MgZz yea PDF has got to be one of the worst specs ever created. It's amazing PDF viewers work at all.


That style speaks to me. Thanks!


COM (Component Object Model) is compatible with C. It's still obviously C++ code being shoehorned into C by explicitly declaring the layout of the object (VTable member), but it does work. You get inheritance that way, and inter-module compatible cleanup. The downside is so much boilerplate code to declare the object, and the performance cost of virtual calls.

You don't necessarily need the complete COM system. For example, you could remove use of the Windows Registry, use of IDL files, remove the `QueryInterface` method, remove the use of GUIDs, remove the class factories (when just a simple 'create' function would do), remove Windows API functions related to cross-module memory management (not using `CoTaskAlloc`). Then it would be portable to systems that aren't Windows. One thing you can't remove is specifying a calling convention for the class methods, because C++ `__thiscall` is not compatible with C code on Windows.


I think Mozilla has historically had a lot of COM outside of Windows. But there's been a goal to remove it: https://wiki.mozilla.org/Gecko:DeCOMtamination

I believe VirtualBox is another project with COM on other platforms. I see lots of GUIDs and HRESULTs in their error messages.

I actually really like the COM style when done well. HRESULT, the idea of somewhat standardized error handling that sub-divides the space of a 32-bit integer into various subsystem-specific error codes, is one of my favorite ideas from there.

Some things are not so nice. For example, everything being a virtual call is not good for performance. Reference counting is also great but over-use of it is also not great for performance (for example, in C++ it's considered poor form to make everything std::shared_ptr<> when you can get away with less).


The goal to remove it applied to internal use, for things which aren't exported. Which makes perfect sense, COM-like interfaces are only needed when you cross module boundaries, and need an fixed ABI.


I find when you get in the groove of the COM style, especially its consistent error handling, you want to use it within modules too.

But I think part of their objections for within a module (from memory, because I haven't looked into their rationale in a while) is it's not worth the performance cost.


It's been reimplemented multiple times. Besides Mozilla's XPCOM there's also StarOffice/OpenOffice.org/LibreOffice's UNO.


My company has at least 3 implementations of cross platform COM (one organic, the rest through acquisitions)


This is more or less what GObject is, which the author mentions in passing. It's an OO system for C, but it does require quite a lot of boilerplate, and you need to manually initialize vtables when creating new subclasses. You also need to manually chain up to the superclass in virtual methods, in many places where it's easy to forget. It's a decent system, all things considered, but it's just a reminder that C's type system is very weak and implementing advanced features all but requires abuse of the preprocessor to avoid unreadable code.


GObject doesn’t abuse the preprocessor. It suggests to #define type-guard parts of the standard boilerplate (purely in spirit of C), but doesn’t require you to do so. Everything else is just C.

Also, being an object system, not only a type system, it is more advanced than C++ in parts of properties and signals. Of course that requires some cooking. For example, Qt had to resort to a custom pre-compiler for that.

That said, I believe it loses quickly in ergonomics to plain Objective C, which is best “practical C + objects” flavor ever done, imo.


Depending on how much one likes to type @.


While you do need to manually initialize a vtable pointer, that pointer can simply point to a const struct which lives in the read-only data section. You don't need to allocate a new vtable with each object or anything like that.

And you can do COM objects in C without using the preprocessor at all (outside of the "are we C++ or not" condition, then you could use real classes instead)


Can you emulate partial function application somehow in C or does the caller still need to supply "this" as an argument to instance methods?


You can emulate partial function application. You also always need to supply some kind of "this" if you don't want your data to live in static memory.


> “Then it would be portable to systems that aren't Windows”

I’ve seen this type of “COM Lite” used for cross-platform plugin and driver APIs. For example Blackmagic Design, a manufacturer of pro video capture hardware, provides an SDK that is essentially identical on Windows, Linux and Mac using this design.


Another example would be the VST3 SDK.


I like COM, as an idea, however besides VB 6 and C++/CX, its tooling has always been clunky at best.

On .NET, the way RCW/CCW works requires understanding both .NET and C++, in other to get it right, and now they decided to also have another way of accessing COM from .NET since CsWinRT was introduced, with quite a bit of boilerplate as well.

The we have the developer groups always keen in pushing ATL, WRL and now C++/WinRT, all with the boilerplate, and after 20 years, still no good VS tooling for IDL files or code generation.

By the way, registration free COM is a thing since Windows XP, when application manifests were introduced.

It is quite ironic that the competition (Borland originally) always had much better tooling to deal with COM, than WinDev is willing to offer to their own developer community.

The way IO and Driver Kit work on macOS/iOS is also COM like.


I haven't really tried out registration free COM yet, I just load the DLLs, instantiate the factory, then instantiate the object.

You can totally do that. DLLs export `DllGetClassObject`, you call that with the GUID you want, and `IID_IClassFactory`, then you get a factory object. Then you call `CreateInstance` from the factory object, and you have your COM object.

Useful for Video Codecs and manually building DirectShow filter graphs. Works whether the codec is properly installed or not.


This is basically what the author is referring to with GObject.


Composition?

    typedef struct { /* ... */ } context;
    typedef struct {
      context ctx;
      /* ... */
    } foo_context;
    typedef struct {
      context ctx;
      /* ... */
    } bar_context;

    void some_general_method(context* ctx, int a, int b);
    void some_foo_specific_method(foo_context* foo_ctx, int c, int d);

    foo_context* foo_ctx;
    some_foo_specific_method(foo_ctx, 0, 0);
    some_general_method(&foo_ctx->ctx, 1, 1);
For checked downcasts, you could include an enum type tag inside `context` and have something like `foo_context* as_foo_context(context* ctx)` for downcasts, which, if `context` is at the beginning of `foo_context`, could just check the tag and cast the `ctx` pointer to `foo_context*` (or do a little pointer arithmetic if `context` is somewhere else in the containing struct). Return `NULL` (or assert) if the tag doesn't match.


+1, this approach will also highlight cases where you're trying to generalize something that isn't as generic as you thought.

I'd argue that the need for downcasting in a method working on the inner context would also be a code smell, and that you might want to reconsider the context split. In some cases, there's no way around it (like async events), but it might be more appropriate to pass additional context or a callback instead, to avoid a circular dependency.


Adding fields to context will break ABI compatibility.

To work around the problem, some libraries add padding fields, and some require passing the size of struct to determine ABI version.


I'm guessing context here is a pointer or a handle?


I first saw this technique used in the late 80's, with the Amiga operating system. Most of the APIs were built on this sort of pattern.


I use this technique a lot and it is very powerful.


What is not considered in the article is to replace pointers to pages and patterns with handles that are tagged indexes into internal arrays.

The big plus is the index tag allows to detect use after free and other memory safety bugs in wast majority of cases greatly improving memory safety.

If one then expose this handle as a generic typedef type over some integer type, then the API will be not type-safe, but the type mismatch will be detected very early.

Another option is to wrap the handle into separated structs for type safety. Then the caller will need to convert from specific handle for page, pattern etc. to the base handle when calling common draw operations. But that will be a simple operation like page.base or pattern.base, not MYLIB_PATTERN_TO_BASE(pattern). The drawback is that the caller will be able to construct wrong handles via struct initialization, but that is a big abuse and the type mismatch will still be detected at runtime.


This is a nice concrete example of a situation where inheritance is useful for program design.

I think i'd go for the "object oriented" approach, but with convenience functions to avoid explicit upcasts. Start with three types:

  cairo_t /* a generic context, could be a page or a pattern */
  cairo_page_t
  cairo_pattern_t
Functions only defined on pages take a page:

  void pdf_page_cmd_bob(cairo_page_t* ctx);
Functions defined on both take a generic context:

  void pdf_ctx_cmd_l(cairo_t* ctx, int x, int y);
Then you need some way to upcast from the child types to the parent (which would be implicit in C++ etc):

  cairo_t* pdf_page_to_ctx(cairo_page_t* ctx);
  cairo_t* pdf_pattern_to_ctx(cairo_pattern_t* ctx);
So a call looks like:

  pdf_ctx_cmd_l(pdf_page_to_ctx(page), 10, 20);
But we can generate this:

  void pdf_page_cmd_l(cairo_page_t* ctx, int x, int y) {
    pdf_ctx_cmd_l(pdf_page_to_ctx(ctx), x, y);
  }
Which lets users write this:

  pdf_page_cmd_l(page, 10, 20);
The convenience functions could even be macros. There would be no loss of type safety from using macros that way. There would need to be a lot of convenience functions or macros, but they are trivial, and so could be generated by a script (or another macro!).


I have implemented this using the gcc/clang “transparent union” extension, which eliminates the need for explicit casting or helpers.

https://gcc.gnu.org/onlinedocs/gcc/Common-Type-Attributes.ht...


I know it isn’t really appropriate to the spirit of the article, but it seems like in this case there is a right answer, and it’s “Fully separate object types” - it’s explicit, prevents errors, is complete, and while it requires a lot of typing to implement it doesn’t require much complexity.


Yeah I agree. Macros can be used to avoid some of the typing in defining the interface when the implementation really is common, but unfortunately that makes it harder to document the generated interface. I wish doxygen had some way of supporting comments for macro-generated interfaces.

In defining the implementation, it's easy enough to do something like this (if the implementation really is common):

    static int pdf_ll_foo_impl(pdf_ll_ctx_t c, pdf_ll_type_t t, ...)
    {  
       //real implementation goes here, perhaps switching on t
    } 

    int pdf_page_foo(pdf_page_ctx_t c, ..) { pdf_ll_foo_impl((pdf_ll_ctx_t) c, PDF_PAGE_TYPE, ..); } 
    int pdf_pattern_foo (pdf_pattern_ctx_t c, ..) { pdf_ll_foo_impl(pdf_ll_ctx_t) c, PDF_PATTERN_TYPE, ..); } 
which you can also use macros to help generate if you want to.


In which of the listed requirements of the article is this approach better, and why?


I think it checks every box except “minimize the number of functions exposed”. Also, if your reasons for minimizing number of functions is about usability and complexity, then many functions that are all easily understood at once (and easy for the compiler to type check) might not substantially fail there either.


API stability is THE #1 most important thing to get right.

I don’t care about the details of your API. I have worked with API’s that triggered a WTF every 5 secs. But I don’t care. I figure it out, build an abstraction on top that makes sense to me, and move on with far more important things.

So do what Linus does with Linux. Don’t f* break clients! Add a separate new API instead or expand the old API with additional entry points. Never ever deprecate anything in your API!

API developers that think it makes sense to break all clients, and force those clients to make changes, just because they wake up one day thinking that renaming FooBar to BarXk is a good idea, makes me want to throw my PC out of the window.

And who actually thought that marking things “deprecated” is a good idea? It is still an API change. With annoying warnings/errors added as a bonus.

Don’t. Do. It.

Use your brain to figure out how to accomplish the same task without breaking clients. The Linux guys can do it. So can you.


This seems perfect for _Generic.

https://en.cppreference.com/w/c/language/generic


> expose the full functionality of PDF directly

Is the objective to provide a PDF Swiss Army Knife? This requirement (if taken literally) precludes the API designer from enforcing much of a coherent mental model.

In terms of cheating, is one pattern format superior to the others? Could you pick one, only allow drawing commands for that one kind of pattern, then have the only method of rendering to the page take one or more of those patterns?

My ignorance of the PDF format is vast, and the question may be obviously absurd to anyone with the least bit of experience.

As an aside, this reminds me of the quagmire I have unsuccessfully wrestled with the various times I've tried to wrap my head around OpenGL


I've had a lot of ideas for cross-language libraries that would need a C API, and this issue always comes up. The idea I had several years ago---but never implemented, because most of the projects I'd use this for are in limbo because I never seem to finish anything---is an API with only one function, which takes JSON and returns JSON, possibly via JSON-RPC. Basically a library that pretends it's a remote service. Slow, yes, but not as slow as some alternatives, and it makes FFI setup with other languages easy.


> is an API with only one function, which takes JSON and returns JSON, possibly via JSON-RPC.

I've done this one, and once only. I wouldn't do it again because the pain point is the lack of typing.

Yeah, yeah, I know, you've read all these blogs everywhere about how C is not type-safe, how C is weakly-typed, etc, but it's a damn sight better than runtime errors because something emitted valid JSON that missed a field, or has the field in the wrong child, or the field is of the incorrect type, etc.

If you're sending and receiving messages to another part of the program, using an untyped interface with runtime type-checking is the worst way to do it; the errors will not stop coming.

Every single time your FFI functions are entered, the function must religiously type-check every parameter, which means that every FFI call made has to now handle an extra possible error that may be returned - invalid params.

Every single time your FFI function return, the caller must religously type-cechk the response, which means that the caller itself may return an extra possible error - bad response.

Having the compiler check the types is so much better. C enforces types on everything[1], almost everywhere. Take the type enforcement.

[1] Unless the type check is explicitly and intentionally disabled by the programmer


Software that takes in text and outputs text is literally every command line program.


Yes, but it's rare to see linked libraries that use this as their API, even though it would greatly simplify FFI.


That's the exact point. Why would someone use a linked library if speed doesn't matter and they are passing text back and forth to be parsed?

That's a terrible way to use a FFI and you would still deal with all the tricky parts.

If that's what you need, you would write a separate stand alone program and call that.


Sometimes I wonder whether C (the ISO programming language) will ever diverge from C (the de facto FFI and ABI for all systems ever)

I expect no but I hope yes


In practice that has always been the case, if you do a random pub quiz, what many will answer will be "my C compiler does XYZ", not "ISO C defines XYZ".

Hence why then you see those rants when they get surprised trying to port to another platform, or compiler vendor, or having optimizations sneak on their back that are perfectly valid from ISO C point of view.


I think the answer is function pointers.


Of course, you don't meed to recreate a full OOP system, to get the benefits required here.

OTOH, it would make a terrible, or at least highly unorthodox API.


This is a few paragraphs about the C API of cairo, postscript and pdf and it doesn't seem all that insightful.

I wish blog posts wouldn't use some random movie reference as clickbait when they could explain what their post is about instead.


> you can have functions like pdf_page_cmd_l(page, x, y)

oh, no, please don't.

just use c++ and you can have namespaces or classes to control visibility - there is no need to take on all the other c++ stuff if you don't want it.


Even if you're using C++ internally, you're likely exposing a C API behind extern C, so you don't have access to those features at the API boundary.


But then it stops being a general tool that is used by python, PHP, java, lisp, C++, rust, nim, zig, lisp and becomes a specific tool for C++ programs.


C is truly the only universal common denominator for all other languages, C++ indeed is only for itself.


How would you use that in this case? These cmd_l ops are just similar ops to different objects, like pen on paper vs. brush on canvas. They don’t operate on a single type of object.

I’d say that C++ way is a bad idea here, because it usually begs for some compoinherimorphism with operator overloads that makes things 10x worse for the cost of one additional signature.


namespace pdf_page { dunno cmd_l (whatever); }


This is purely cosmetic and doesn’t save you anything. Also, Cairo API (and most C APIs in general) already use <lib>_<class>_<method> naming scheme. E.g. cairo_svg_surface_create(), gtk_container_get_children().


it is not cosmetic or a naming scheme - the language and compiler enforce it.


None of the concerns are about the name.


Aside from the interlanguage interop issues, c++-like visibility control (via private) makes ABI compatibility essentially impossible.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: