/* foo.h */
struct foo_object;
struct foo_object* foo_create(int flags, …);
/* foo.c */
#include “foo.h”
struct foo_object {
int flags,
…
}; /* foo.h */
struct foo_object;
struct foo_object* foo_create(int flags, …);
/* foo.c */
#include “foo.h”
struct foo_object {
int flags,
…
};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.
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.
>> 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.
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.