Skip to content

Instantly share code, notes, and snippets.

@JoshuaSkootsky
Last active February 19, 2026 20:34
Show Gist options
  • Select an option

  • Save JoshuaSkootsky/ed5b24a3ea91500d23bc382dd58d4ce6 to your computer and use it in GitHub Desktop.

Select an option

Save JoshuaSkootsky/ed5b24a3ea91500d23bc382dd58d4ce6 to your computer and use it in GitHub Desktop.

Ghostty's Anti-Vtable Stance

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

Ghostty's Actual Patterns

  1. 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.


  1. 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);
	    }
	};

  1. 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment