Developer Ben Raz shares a nuanced assessment of Go, praising its concurrency model and type system while criticizing its enum handling, immutability constraints, and error patterns.
Software developer Ben Raz has published a detailed review of Google's Go programming language based on hands-on experience building several projects. The assessment offers a balanced perspective on Go's design choices, acknowledging its strengths while highlighting pain points that impact real-world development.
Raz emphasizes Go's standout concurrency implementation as a major advantage. "Concurrency is not an afterthought in Go," he notes, praising how goroutines and channels are integrated as first-class language features. This approach avoids the "colored functions" problem found in many other languages and provides a robust foundation for concurrent programming. The channel-based model combined with select statements creates what Raz describes as "mostly a joy to work with" for concurrent operations.
Go's deliberately simple type system also earns praise. Unlike languages with complex inheritance hierarchies, Go uses struct embedding as syntactic sugar for composition. This allows methods of embedded structs to be called directly on the composing type. Raz highlights the implicit interface implementation as particularly valuable: "A struct doesn't have to explicitly fulfill an interface for it to apply." This enables flexible use of empty interfaces (interface{} or any) for dynamic typing scenarios while maintaining compile-time safety where needed.
Syntax choices like case-based visibility (uppercase for public, lowercase for private) and minimal type annotation punctuation receive approval for improving coding ergonomics. "It saves typing time and just makes sense," Raz comments, contrasting it with Rust's explicit pub declarations.
However, the review identifies three significant pain points. First, Go's lack of true enums forces developers to use iota constants as a workaround. Raz argues this "doesn't accomplish its primary goal" since there's no compile-time guarantee that only defined values will be used. Switch statements lack exhaustiveness checking, potentially allowing runtime errors from invalid values like State(500).
Second, immutability constraints prove problematic. Go's const only supports compile-time constants, preventing immutable structs, function results, or maps. The alternative—using variables with accessor functions—creates boilerplate and doesn't prevent mutation of underlying data. Raz calls this "an absolutely terrible solution" for exported package symbols.
The most substantial criticism targets Go's error handling. While Raz dismisses complaints about if err != nil verbosity as overplayed, he identifies deeper issues with the error model. The error interface type only requires an Error() string method, forcing consumers to parse strings or use fragile comparisons like strings.HasSuffix(err.Error(), "not a directory"). This loses structured error information and creates maintenance hazards when error messages change. Raz contrasts this with Rust's approach using enums and methods like kind() on std::io::Error that preserve structured error data.
This review surfaces ongoing tensions in Go's design philosophy. Features like implicit interfaces and elegant concurrency support enable productivity, while the absence of enums and constrained error handling create friction. As Raz considers returning to Rust after his Go experience, his observations highlight how language design choices directly impact developer efficiency and code safety.
For those exploring Go's concurrency model, the official documentation provides comprehensive guidance: Goroutines, Channels. Developers comparing error handling approaches can reference Rust's std::io::Error documentation.

Comments
Please log in or register to join the discussion