From Mitchell Hashimoto's blog post and Ghostty source code:
"Zig's lack of built-in polymorphism is a feature, not a bug. We use explicit types and comptime to achieve zero-cost abstractions without hidden control flow."
Ghostty's codebase shows this philosophy:
| Pattern | What Ghostty Does | What They Avoid |
|---|---|---|
| Terminal backends | struct { impl: union(enum) { crossterm, terminfo, ... } } | Backend interface with vtable |
| Rendering | Renderer struct with comptime backend selection | Virtual draw() methods |
| Configuration | Config struct with comptime field iteration | Reflection-based serialization |
| Event handling | Explicit state machines | Observer pattern with callbacks |
- Tagged Unions for Polymorphism
From src/terminal/Terminal.zig:
const Terminal = struct {
// Explicit backend selection, no vtable
const Backend = union(enum) {
crossterm: Crossterm,
terminfo: Terminfo,
windows: WindowsConsole,
};
backend: Backend,
pub fn write(self: *Terminal, data: []const u8) !void {
// Explicit dispatch, compiler knows all cases
switch (self.backend) {
.crossterm => |*ct| try ct.write(data),
.terminfo => |*ti| try ti.write(data),
.windows => |*win| try win.write(data),
}
}
};Key insight: The compiler generates optimal code because it knows every possible backend at compile time. No indirect calls, no cache misses from vtable lookups.
- Comptime-Selected Implementations
From src/renderer/Renderer.zig:
const Renderer = struct {
// Backend selected at compile time based on target
pub const Backend = if (builtin.target.os.tag == .macos and !builtin.target.isWasm())
MetalBackend
else if (builtin.target.os.tag == .linux and !builtin.target.isWasm())
OpenGLBackend
else if (builtin.target.os.tag == .windows)
D3DBackend
else
@compileError("Unsupported renderer target");
backend: Backend,
// All methods inlined, no dynamic dispatch
pub fn render(self: *Renderer, surface: *Surface) !void {
try self.backend.render(surface);
}
};- Explicit State Machines Over Callbacks
From src/terminal/stream.zig:
// No callbacks, no vtables. Explicit state + switch.
const Stream = struct {
state: State = .ground,
const State = enum {
ground,
escape,
escape_intermediate,
csi_entry,
csi_param,
// ... 20+ states
};
pub fn consume(self: *Stream, byte: u8) !void {
switch (self.state) {
.ground => switch (byte) {
0x1b => self.state = .escape, // ESC
0x00...0x1f => try self.executeControl(byte),
else => try self.print(byte),
},
.escape => switch (byte) {
'[' => self.state = .csi_entry,
']' => self.state = .osc_string,
else => self.state = .escape_intermediate,
},
// ... exhaustive handling
}
}
};Written with StackEdit.