Good union types in Go would probably need types without a zero value
Go’s commitment to having a meaningful zero value for every type collides with adding algebraic sum/union types, exposing deeper trade-offs between runtime design, safety, and expressiveness. Commenters argue that Go’s current tools (interfaces, error values, runtime checks) make robust, exhaustively checked error handling and domain modeling awkward compared with languages like Rust, Scala, ML-family languages, or Zig. The thread broadens into whether Go’s minimalist philosophy and backward-compatibility promise justify omitting such type-system features, or whether this leaves Go a “flawed but productive” language that offloads complexity onto application code.
Union / Sum Types vs Go’s Zero Values
- Central question: can Go have “good” union/sum types without breaking its “every type has a zero value” design.
- Some argue zero values for unions are either useless (just memory packing tricks) or dangerous / confusing.
- Others suggest pragmatic compromises: zero value = first variant, or zero value = nil/interface-like “no value”, even if slightly “stupid” but consistent with Go’s other rough edges.
- Concern: choosing “first variant as default” breaks commutativity (
A|BvsB|Adiffer) and can be a footgun.
Error Handling and Exhaustiveness
- Several commenters want sum types mainly to improve error handling:
- Avoid repetitive
switch/errors.Is/string-matching patterns. - Get compile-time guarantees of “handled all error cases” and easy refactoring when error variants change.
- Avoid repetitive
- Comparisons to Rust, Scala (ZIO/Cats Effect), Zig, Nim:
- These offer more precise error typing or effect systems, but still don’t perfectly answer “show me all possible errors here.”
- Some find Rust’s error ergonomics disappointing in practice; sum types can demand lots of boilerplate and wrapping.
Runtime and GC Constraints
- Big technical objection: Go’s concurrent GC needs to know where pointers are.
- A tagged union whose active variant changes could change which fields are pointers, racing with the GC if done naïvely.
- Go previously changed interface representation to avoid similar GC races; unboxed tagged unions might require deep runtime redesign.
- Boxing every variant (like interfaces) is feasible but adds allocations and undermines performance motivations.
Expressiveness vs Simplicity
- Many feel Go’s type system is too weak for serious domain modeling (e.g., “make invalid states unrepresentable”; non-zero-only types).
- Others report very high productivity with Go and view stronger type systems as added cognitive load, especially for large teams of relatively new engineers.
- Tension: minimal core language vs pushing complexity into codebases (custom patterns, libraries, boilerplate).
Did Go ‘Ignore’ PL Research?
- Some say Go failed to adopt 40–50 years of known ideas (sum types, richer generics, algebraic data types).
- Counterpoint: adoption, readability, tooling, GC, and compatibility constraints justify caution; features can’t just be copied from ML/OCaml.
- Go’s compatibility promise and lack of a story for evolving enums/sums without breaking users is cited as a blocker.
Workarounds and Partial Solutions
- Current practice: interfaces plus type switches; sealed interfaces with unexported methods; tooling for exhaustiveness checks.
- Proposed syntactic sugar: special
option/result-like constructs usingmakeand type assertions; would still panic on misuse, consistent with other Go nil/zero traps. - Some prefer to “just use another language”; others note they’re constrained by employer choices, so they keep pushing for better features in Go.