
C Interfaces and Implementations describes how to use interface-based design in the C programming language, and it illustrates this approach by describing 24 interfaces and their implementations in detail. The source code in the book is interleaved with its explanation in an order that best suits understanding the code.
Wikipedia has a very nice explanation - https://en.wikipedia.org/wiki/Literate_programming
A good way to think about it is {Program} = {set of functional graphs} X {set of dataflow pipelines}. Think cartesian product of DAG/Fan-In/Fan-Out/DFDs/etc. Usually we write the code and explain the local pieces using comments. The intention in the system-as-a-whole is lost. LP reverses that by saying don't think code; think essay explaining all the interactions in the system-as-a-whole with code embedded in as necessary to implement the intention. That is why it uses terms like "tangle", "weave" etc. to drive home the point that the program is a "meshed network".
To study actual examples of LP see the book C Interfaces and Implementations: Techniques for Creating Reusable Software by David Hanson - https://drh.github.io/cii/
People have been writing C code with ADTs and "Modules" from the very beginning.
Two excellent examples which come to mind are; Andrew Tanenbaum's Minix book Operating Systems Design and Implementation and David Hanson's C Interfaces and Implementations: Techniques for Creating Reusable Software.
And of course the Linux Kernel is full of great modular C techniques which one can study.
His code is here: https://github.com/drh/cii
When I have such a situation, I'm inclined to write myself my own pre-processor (as I did for Pascal once in a previous millenium, on an Atari ST 520+), so that you can write in a style that is nicer to program in, which gets pre-compiled into something that is nicer to use from your client code.
Nothing comes without downsides: the price of this is that other developers need to understand your idiosyncratic pre-processor, so this method works best for "single author" code/personal projects.
What you don't want in a team is each coder having their own way of doing things, so that you cannot share libraries and helper functions.
BTW, the best book on the OP's topic of production coding in C and implementing type-safe ADTs is Chris Hanson's book "C: Interfaces and Implementations." It contains some eye-opening tricks even for experienced developers in (standard) C.
However, you should consult the book "C: Interfaces and Implementations" by Dave Hanson, which has a library called "Arena" that is very similar to your "Pool"s, and it shows a few more tricks to make the code safer and easier to read (Chapters 5+6, 6 in particular).
D. Hanson's book (written in the 'literary programming' style invented by Donal Knuth) also demonstrates having debugging and production implementations for the same C API to catch memory errors, and his book is from 1997:
> Copyright © 1997 by David R. Hanson
(He used to be a SE professor I Princeton before getting hired by Microsoft, I believe.)In order to speed it up by reducing the number of malloc() calls, it may be worth adding a simple arena memory allocation measure: by allocating one larger block (e.g. 1 MB) initially and then doubling the memory size each time you run out, all malloc()/calloc() calls can become local salmagundi_alloc() calls that are just macro invocations that return an arena buffer pointer and also increment said pointer as a side effect.
I also recommend you have a look at Chris Hanson's book "C: Interfaces and Implementations", which has a few neat C API tricks that your code could benefit from (e.g. for reducing name space pollution, for avoiding null pointer argument errors, API method naming etc.).