- typedef struct { ... } foo
- foo *foo_create()
- void foo_destroy(foo *)
- a bunch of functions that take foo* as their first arg
which is kind of the same as a class and only more error-prone.
I say this as someone who actually _likes_ C, but the manual memory management model is very often unnecessary, confusing, repetitive. There was an idea some time ago of a language extension that would extend the concept of automatic storage duration to allow an explicit destructor to be called when the variable goes out of scope, like <close> variables in some languages. I genuinely think things like that would make the language a bit more ergonomic without fundamentally changing its nature.
foo f;
foo_init(&f);
foo_destroy(&f);
...
foo *g = malloc(sizeof(foo));
foo_init(g);
foo_destroy(g);
free(g);I would write the allocation as
foo * const g = malloc(sizeof *g);
to avoid repeating the type name and "lock" the allocation to the variable. If the type on the left hand side ever would change, this still does the right thing.IIRC clang implements it as well, but I wasn’t able to find a reference to it in their docs.
There is a much more advanced design and implementation at “A defer mechanism for C” (December 2020): https://gustedt.wordpress.com/2020/12/14/a-defer-mechanism-f...
For my own purposes, I think I can live without handling stack unwinding so I continue working on my pre-processor.
Since the pre-processor is not yet finished, there I use a vector¹ of {.pointer, .destructor} where I put objects after initialization, with one macro at the end of each managed scope that calls the destructors for that scope in reverse order, then another macro meant for the function exit points that calls all the destructors in that vector. This has been built many times before by other people of course, it’s just an exercise to see which difficulties arise.
¹ Vector, growable array: I did my own trivial type-generic growable array, with the classic {.capacity, .len, .items}, but again there are many previous implementations. The two I’ve found more interesting are:
- “C Template Library (CTL)”: https://github.com/glouw/ctl
- “Klib: a Generic Library in C”: https://github.com/attractivechaos/klib/
https://github.com/tezc/sc/tree/master/array
It is just an array of your type, e.g int *numbers, so you have type info in debugger as well.
#define P
#define T int
#include <vec.h>
#define T vec_int
#include <deq.h>
A deq_vec_int - analogous to std::deque<std::vector<int>> - is a neat example. struct X { int a; }
struct Y { *X; int b; int c;}
void add(Y* self, int number) {
self->a += number;
}
Y y;
y.a = 10; // composition
y.add(1) // y.a = 11 now
This alone would simplify C coding so much without taking any power out of it.The other extension i would add is some sort of interface or protocol.
As soon as you can do something like "y.add(1)", having a generic contract to refer to things without having to know its concrete type is some of the good things from the OOP world.
With this you would also be able to call some cleanup code and even a initializer.
This is still C and its still much simpler than C++, and yet almost as powerful.
C should propose these kind of things even if it was not that conservative and it would retain a lot of coders that migrate instead giving C barely evolved from its 70's roots.
Unless you decide you use libgc, presumably?