An unordered list of things I miss in Go

Go’s minimalist design—lacking features like default/named arguments, explicit nullability, enums, sum types, and ordered maps—prompts both praise and frustration from developers comparing it to languages such as Python, C#, Rust, and Java. Commenters debate whether Go’s choices (like deliberately randomizing map iteration order and relying on explicit error returns and nil pointers) improve robustness and simplicity or instead create unnecessary boilerplate and sharp edges. Many agree that while Go’s tooling, performance, and single-binary deployment are strong draws, its ergonomics around data modeling, iteration order, and error/nil handling remain significant pain points.

Nullability, nil, and type safety

  • Strong push for explicit nullability types: compiler-enforced non-null refs (Rust Option, C# nullable refs, Kotlin-style) are seen as a major safety win; dereferencing nil should not compile.
  • Counterpoint: Go’s design favors simple types and explicit error handling over a richer type system. Many accept nil as a trade-off; some argue nullability adds complexity and isn’t aligned with Go’s philosophy.
  • Practical pain points: modeling DB columns, JSON “absent vs null vs zero value”, and unsafe a.b.c chains. Workarounds include pointer-as-optional, Null[T]/Optional wrappers, or sql.Null*, all with downsides.
  • Debate whether Go could ever retrofit nullability or sum types without breaking compatibility; some think nullability is easier than full unions, others doubt either will happen.

Map iteration order, randomness, and ordered maps

  • Go maps randomize both hash seeding and start offset on each iteration. This is justified as:
    • Hash-DoS mitigation.
    • Enforcing the spec’s “unordered” promise and smoking out code that accidentally depends on order.
  • Critics find per-iteration randomization surprising and harmful to reproducibility, debugging, and simple tasks like printing stable CLI help output.
  • Comparisons: Python dicts preserve insertion order by design; Rust HashMap has per-map seeding but deterministic iteration; Java offers LinkedHashMap and TreeMap.
  • Strong subthread on terminology: unordered vs ordered-by-insertion vs sorted-by-key; hash maps vs trees; linked vs index-based ordered maps.
  • Some want an ordered map (insertion-ordered hash) in Go’s stdlib; others say:
    • If you need consistent order, sort keys (slices.Sorted(maps.Keys(m))) or use a tree/other structure.
    • Ordered hash maps add overhead and ambiguous performance expectations.

Default/named arguments and functional options

  • Lack of default/named args is viewed by some as a feature: it forces API designers to think, and you can use wrapper functions.
  • Others miss them for readability, especially around multiple bools or rarely-changed parameters; they cite Python and C# as more ergonomic.
  • Functional-options pattern is a common Go workaround but criticized as:
    • Verbose for general functions.
    • Awkward when implemented via closures (no equality, hard for middleware/testing).
    • Better when using typed or interface-based options.

Error handling ergonomics

  • Many dislike repetitive if err != nil { return ... } and want a ?-style operator or automatic propagation.
  • Others embrace explicit error returns as core to Go, using helper must()/must1() with panic for scripts or debugging.
  • panic/recover are noted as Go’s de facto exceptions, but mixing them with error is seen as confusing in serious code.

Go’s philosophy vs feature wishlists

  • One camp views omissions (nullability, enums, sum types, ordered maps, default params) as necessary constraints that keep Go simple and maintainable.
  • Another sees Go as dated: they want richer type systems, sum types, better null handling, enums, and more ergonomic syntax; some look to Borgo, Rust, C#, or Swift as “Go but with modern features.”