I have a big non-generic struct that has one data field that is ?*anyopaque. The field itself is used by casting the pointer to a known type then using it.
However, when deinitializing the big struct I do not know what the type is.
My best current idea is to add another field next to data called free_data that is a function pointer, and i would assign this function from the same routine that creates the data itself. But I am hoping for something nicer.
The enum idea is interesting. Is there a way to compare the type the anyopaque is pointing to a list of known types somehow? all with the deinit function.
If you’re asking whether you can figure out the type from the value pointer, no, the type only exists at compile time. You have to add your own type info (like an enum) and map that to the type yourself.
You could use pointer tagging to place the enum within unused bits of the anyopaque pointer, but that means that you need to mask away those pointer tagging bits before you can convert the anyopaque pointer back to a pointer to the concrete type wherever that happens.
Depending on alignment and architecture there are different amounts of bits that could be used for pointer tagging, it is a fairly common technique in interpreters.
It is a bit hackish and not for every scenario, but you can prefix the memory with some metadata to indicate its size and/or type. I once did such a thing for to support a custom allocator used by a C library, which simply expected the basic malloc, realloc, and free signatures, which do not play nice with Zig’s model of allocators, which expect a known size that must match the size allocated when freeing it.
I simply allocated an additional few bytes when it called malloc, wrote the size, and then returned the offset pointer after the size. When free or realloc was called, I merely decremented the pointer by the same few bytes, and read the size. This same concept could be extended to store whatever you need, such as an enum value indicating the type, etc.
It’s also pretty easy to make this pointer tagging approach type safe in zig.
If you use the lower bits, the number you can use generally just depends on the alignment of the data you point to and is quite portable. So up to 3 bits for 8 byte aligned pointers.
If you use the upper bits you usually have more space but it’s architecture (and even cpu) dependent how much space you have and sometimes it might not even be possible because some other metadata might be stored there(like PAC and MTE on ARM). Sometimes you also have to mask your data off while in more recent times the cpu does that for you.
On x86_64 you have either 64-48=16 bits or 64-57=7 bits for 4-level and 5-level paging respectively. On ARM this depends on the page granularity and also on the version because at some point they also added another level.
Given that you know the type at some points, ideally you could extend that knowledge to deinit.
If that is not possible then only way is to include it in the runtime data.
There are 2 ways to do that without an extra field (that I can think of)
header on the allocation. I suspect you are interacting with a c library, a header is how libc can free allocations despite the language having no way to know the type