Week 7 - Interface Values, itabs, and Dispatch Cost¶
7.1 Conceptual Core¶
- A Go interface value is a two-word header:
- For non-empty interfaces (
io.Reader,error, etc.):(itab, data). - For empty interfaces (
any/interface{}):(type, data). itab= interface table = the dynamic dispatch vtable for one (interface, concrete type) pair. Holds the type pointer, the interface type pointer, a hash, and an array of function pointers (the methods).datais a pointer to the concrete value, or the value itself if it fits in a word (small integer types, etc., as an optimization in some Go versions). Modern Go (>=1.4) always uses an indirect-be careful with old assumptions.
7.2 Mechanical Detail¶
- Read
src/runtime/iface.go. Key functions:getitab,convT2I,assertI2I,assertE2I. Thegetitabcache is keyed by `(interface_type, concrete_type) - first call may allocate the itab; subsequent calls hit the cache. - Cost of an interface call:
- Load
itabfrom interface header (cache hit). - Load function pointer from
itab.fun[N]. - Indirect call. Plus: the call cannot be inlined. So an interface call is 1 indirect call + lost inlining opportunities. On hot paths, this matters.
- Boxing cost: assigning a non-pointer concrete value to
anyallocates if the value is larger than a word.var x any = 42may allocate (depends on Go version's int boxing optimization);var x any = SomeBigStruct{}definitely allocates. - Type assertions:
v.(T)panics on failure; allocates anitabifTis an interface.v, ok := v.(T)is the same but no panic.switch v := v.(type)is the same machinery, optimized for multiple cases.- Type switches are faster than chains of type assertions because the compiler may emit a hashed dispatch table.
- Generics vs interfaces: generics (since 1.18) compile to a single generic body parameterized by the GC shape ("GCShape stenciling"), with a per-shape dictionary. Generics are not specialized like Rust monomorphization-there is still indirection for method calls on type parameters. The performance vs interface tradeoff is subtle and workload-dependent. Read
compiler/internal/types2/and Russ Cox's GCShape blog post.
7.3 Lab-"Interface Bench"¶
- Build a tight loop calling a method via three paths: concrete type, interface, generic type parameter. Benchmark with - benchmem`.
- Inspect the disassembly with
go tool objdump -s 'main\.benchInterface'. Identify the indirect call. - Refactor a real-world pattern (a
Loggerinterface used 10× in a hot path) into a concrete type or a type-parameterized version. Measure the win or non-win. - Build a worst-case allocation example: passing a stack int into
fmt.Println(...). Show with - gcflags=-mthat the int escapes (boxing intoany). Replace withfmt.Println(strconv.Itoa(x))` and re-measure.
7.4 Idiomatic & golangci-lint Drill¶
gocritic: typeAssertChain,gosimple S1034(omit comma-ok in type-switch). Re-read the Go FAQ on "Why no implicit type conversions?"-the answer informs API design.
7.5 Production Hardening Slice¶
- Add a benchmark to CI that asserts
0 allocs/opon critical paths (e.g., the request-handling hot path of your service template). Usetesting.B.ReportAllocs()and a script that diffsallocs/opagainst a committed baseline. Any PR that introduces an allocation on a0-allocpath fails CI.