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