Let’s say I have an std.mem.Allocator, which might or might not be a concrete MyAllocator. Is there some way I can “downcast” to MyAllocator (with appropriate runtime checks), so that I can take advantage of extra APIs provided by MyAllocator without necessary making the type of the allocator concrete throughout the stack?
I think I can do that by comparing whether vtable is the same as the one I have, using the code roughly like this:
But is this pattern sound? I know that comparing function pointers for equality is fraught with peril, at lest in Rust. What about Zig? Would the code above do what I want it to do, or are there any sleeping dragons?
What are you going to do if the allocator isn’t what you’re expecting? Presumably, just crash, at runtime, for something that should have been caught at compile time. I recomend creating your own interface, with the methods that you need. Make it so this interface can create a std’s Allocator. When you need to interface with code that expects the regular std’s Allocator, you just create one and pass it.
In my case, it’s fine to do nothing — the extra functionality is for recording extra metadata for debugging, and, if the allocator is different, the correct behavior is indeed to do nothing.
And I want the code to be polymorphic in the allocator, so that it can be used with my debug allocatore, but also with any other allocator if the caller can’t be bothered to use mine.
I think you would still want your own interface. Make the function pointers that you don’t require as optional. Make a convenience function that simply takes a normal allocator and creates your custom allocator, with the optional fields set to null.
Another possibility is to receive your allocator as anytype and check at compile time if it is your custom allocator or the std Allocator.
Why compare the whole value of the vtable structure? I would just compare the vtable pointers. As long as there is only ever one value for my_vtable it shouldn’t be problematic.
vtable is a field and its address is different in each Allocatorinstance. But you need a pointer that is different for each allocator concrete type but is the same for different instances of that same type. This is why @matklad is comparing all function pointers assigned in the vtable.
Interesting example. Taking the address of a literal must be static. It’s not allocated and it better not be on the stack, so it must be static. I’ve never used this pattern myself though.
Is it truly global or static per CU? In general it is safer to compare values than addresses. Values here are addresses of alloc, resize and free and if all of them match hopefully it is not a false positive.
alloc, resize and free are also just pointers to static symbols, so it’s pretty much exactly the same thing. Comparing three pointers instead of one doesn’t really make it any safer.