In Zig, vtables (virtual tables) are a manual pattern used to implement dynamic dispatch, allowing different types to be used through a single, shared interface. 12345 Unlike languages like C++ or Java, Zig does not have "classes" or "interfaces" as built-in keywords. Instead, you construct vtables yourself by creating a struct of function pointers. 678910
A typical Zig interface is a "fat pointer" consisting of two main parts: 311
- A Context Pointer (ptr): A pointer to the specific implementation's data, usually typed as
*anyopaque. - A VTable Pointer (vtable): A pointer to a constant struct containing function pointers that define the interface's behavior. 31213
The most prominent use of this pattern in the Zig Standard Library is the Allocator. It allows functions to accept any allocator (like ArenaAllocator or GeneralPurposeAllocator) without knowing exactly which one is being used at compile time. 389
// Conceptual structure of a Zig VTable
const VTable = struct {
alloc: *const fn (ctx: *anyopaque, len: usize, ptr_align: u8, ret_addr: usize) Error![]u8,
resize: *const fn (ctx: *anyopaque, buf: []u8, buf_align: u8, new_len: usize, ret_addr: usize) bool,
free: *const fn (ctx: *anyopaque, buf: []u8, buf_align: u8, ret_addr: usize) void,
};- Explicit over Implicit: You must manually define the vtable struct and the "wrapper" interface struct. There is no hidden "vptr" added to your data structs by the compiler.
- Comptime Known: Most vtables in Zig are created as const anonymous structs, which the compiler often places in read-only memory.
- Performance Trade-off: Using a vtable introduces a "lookup indirection" (the CPU must follow a pointer to find the function), which is slightly slower than direct function calls and can hinder some compiler optimizations like inlining.
- Alternative (anytype): For better performance, Zig developers often prefer Static Dispatch using anytype or comptime generics, which resolves the call at compile time and allows for full inlining. 13411131415