| I totally agree. After working with it for a while I feel like its best use is in writing CLI tools due to the compiler making it so damn easy to produce statically linked binaries for any platform, backed by an incredibly powerful standard library. My issue for application and web server development are in the language design as it restricts me in my personal quest to write loosely coupled code that is marinated in unit tests - at least when compared to other languages. That said I enjoy the language, have learned a lot from it, use Go often (previous day job) and have a gopher plushy - but I am just not a die hard loyalist. There are things about the language that blow others out of the water, but there are significant portions that leave a lot to be desired. What I have noticed is the language design has inconsistencies and there are lots of exceptions built into the language to get around missing features; features which are sometimes filled in later leaving ambiguity around approaches. For example, Go uses a nominal type system with the famous exception of interfaces with methods that are structurally evaluated. The issue is interfaces only feature structural type evaluation on the top level, anything nested beyond that is nominal. So an interface that has a method which returns an interface requires an implementing struct to return the _exact_ interface from that same method. You can accept a struct where an interface is a parameter but you cannot return a struct where an interface is the return type. This is useful when building type safe dependency containers, something that makes unit testing much easier. Instead people just overuse `Context`, prop drill it into everything and cast types at some point when trying to get things from it. Other examples are the return types from functions. Go didn't have generics initially so a Result[T] wasn't possible - but also exceptions were not possible. Tuples were not allowed by the type system but the language makes an exception for the function return types - fair enough. Without type parameters and a need for generic basic types like `map`, `slice` and `sync.Map`, the language made an exception for the the primitive types - giving them generics however this is now inconsistent with the current implementation of generics. Usage of `make()` and the confusion brought on by managing "references" also adds a bit of friction to the language. Generics are inconsistent as well - for instance you can have a struct with a type parameter but not methods - however you can have functions with type parameters that accept a struct as their first parameter. The tooling is a little underpowered, the test coverage analysis tool doesn't consider branch coverage, only statement coverage so you can have 100% test coverage reported with only 50% truly covered. Making mocks requires cumbersome type generation - this is something I am okay with but the types generated have terrible assertion capabilities. But there are phenomenal things from the language. Packages are a great design choice, module management from git is simple and effective, `range` is chefs kiss, the compiler is beautiful. I love the idea of goroutines but I'm not blown away by channels. They are fancy iterators with dedicated keywords which I find can clutter things up a little - but they were really cool when Go first came out and we were only just starting to think about how to manage asynchronous concurrency. |
Possibly I'm misunderstanding the complaint, but that is because an interface is a fat pointer, so one returns a pointer to a struct implementing the interface.
https://go.dev/play/p/YOa6NF7rjPF