I like simple languages without bells and whistles but for me, Go has some flaws that I cannot look past.
Specifically the map type. Unlike some Map implementations in Java, maps in Go are not ordered and there is no way to order them unless you want to keep a slice representing the order of the keys. Keys in these maps must be comparable, which means that unlike Java, you can't just implement equals() and hashCode() to make your type comparable. Maps and slices are not comparable, so if you use either in a struct, that struct is no longer comparable. Since map keys must be comparable, you'll have to convert your struct to a comparable type like a string if you want to use it as a key to a map.
You can always look for a third party library for more data structures, but the last time I looked those libraries didn't support generics yet.
Sometimes you'll find a standard library function that takes an interface{} and might panic if you give it a value it doesn't like. This is especially the case for library functions that want to operate on slices. Slices can be generic, but you'll still find many functions that accept interface{}, so you're left with library functions and third party libraries that take any as the type of an argument and expect you to read the documentation so that you don't accidentally pass the wrong type.
interface{}s are comparable sometimes, and if you compare two types that aren't actually comparable, you'll get a panic.
Maps being unordered is an intentional "feature" to prevent one of these programming adages that every bit of behaviour, every API will become something developers rely on; if a map has to be ordered, you're restricted in what implementations you can use, or forced to keep an array with the element order alongside it, again limiting performance. Most use cases of map do not rely on insertion / iteration order.
You might want to check in again. Generics have been in go for a couple of years now. There are loads of generic data structure libraries now. The most recent release of go included a way to generic structures with `for range` loops. Which unblocks the ecosystem from writing iterator helper libraries.
Although there are some decent libraries out there for this kind of thing, my complaint mostly stems from the fact that a lot of existing code doesn't involve generics yet.
I think I remember skimming an article about for range loops on Hacker News a while back, but again, my main complaint is still that existing code exists in a way that doesn't incorporate all these new features yet.
I’ve recently been tinkering with Rust and, while there have been countless articles praising its safety features, it’s the enums and match-expressions that are my favourite features. I want so much for Go to get these but I don’t know that it will.
I get a little sad from the folks that so desperately want Go to be “Done” when there is a bunch of stuff that the language can add and be better for it.
This is a fair take. Especially after reading the two linked blogs (which have also enjoyed some time here on HackerNews).
I have personally fallen into the category of "you can say you don't like Go, but you can't say it's not well designed". I've admitted as much here in years past. What has changed though is that I've written a few more apps/services with it and come to love the no-nonsense value-add. Go compiles fast, the tooling is great (The LSP for NeoVim just works) and the code written by seniors isn't vastly different than code written by mid/juniors. While I'm still not wild about the community attitude of "just build that yourself" I'm glad that the standard lib includes enough to make it possible.
I'll take the boring "easy to read" code over the "let me show you how much I know about Category Theory" most days. Please excuse the dig on FP - I do truly love OCaml, Elixir and now Gleam... I also just like the simple life some days.
> While I'm still not wild about the community attitude of "just build that yourself"
I kinda like it; the Node community showed what happens if every triviality ends up in a library, and optimizing dependency management is one of the primary goals of Go. It's not even a language feature - there's nothing stopping anyone from creating 1000+ github repos with trivial utility functions.
That said, there will be a library at this point for anything nontrivial.
I feel like this article misinterprets at least one of the points made by the articles it cites. There's a bit in here that roughly goes: "the filesystem api is geared towards Unix and doesn't work that well for windows, but that's ok, because it's designed for servers which run Unix". And that's perfectly valid, and I agree with this 100%, but it misses the broader point that's being made in the "I want to get off Mr golang's wild ride" post.
The problem that the wild ride post has is not that the filesystem api doesn't work for windows. It just uses it to illustrate a broader point: golang opts for silent failures (for example by just making up file permission bits for a file on windows), and just chugging along pretending everything is fine when it's not. This design choice goes well beyond the filesystem api.
Now, whether I think that's a fair criticism or not is beside the point. All I'm saying is that this post is not a valid response to the criticism illustrated by comparing Rust's filesystem api to that of Go.
This and similar post are a bulletproof way to start a flame war.
Last time it was generics that were missing, now everyone is raging about sum times and of course explicit error is a topic of constant concern and why panics and not exceptions?
Go is well designed to build good software quickly. Easy dependency handling, good tooling, vast ecosystem.
Go is well designed to help developers with automation and help them catch mistakes, that's why it's easy to parse and all language design decisions take that into account.
It's also designed to produce a lot of code and that requires the language to be easy to understand and programs easy to tweak and that's what it provides, since you'll have a lot of developers tweaking the code.
We're in the industry of shipping different kinds of products and that imposes different constraints and results in different languages being used. Also, different people care about different stuff and languages form clusters of similar minded people around them, that's a choice too.
Hard disagree here. Instead of one type of error handling, there are two: panics and err. Panics could effectively be replaced with os.Exit() if you really wanted to exit.
If anyone says Go is Great without addressing this issue, they've drunk the koolaid.
panic/recover is good and necessary to have, Rust also has this. Swift’s fatalError is not recoverable which is very frustrating.
I don’t like go much - languages that can have nil pointer errors can take a hike, but what I like even less than a nil pointer error while handling a request, is for my whole process to die unrecoverably because someone forgot to check a nil in an infrequently called API. Or when comparing something that’s not allowed to be compared. Like the language would be better if these mistakes were impossible to make, but as long as we have a language with a plethora of garden variety syntax can panic like `a.b` or `a == b` we should be able to mitigate that risk at runtime.
In golang, `panic()` and `return err` are two different ways of unwinding the stack. 80% of my error handling in go is `return nil, err`. But there's no reason syntactic sugar couldn't be applied for something like `when err { return nil, err} { err := doSomething() }`
I think this is an unneccesary absolute take. Panic is rarely used for error handling and nobody is advocating for its use in practice. I wouldn't use os.Exit() in deeper layers of code myself, it's better to have OS level startup / shutdown in a single / central location so that the other layers of a nontrivial application don't need to be aware of it. Having a recover block somewhere allows for graceful shutdown and logging, whereas using os.Exit() in random places would require you to write a utility function or repeat the shutdown / logging procedure.
I think V [1] is what Go should’ve been. Simple, compiles fast, integrated language tooling, in fact quite similar to Go, but without all the dumb design decisions. Unlike Go, it has sum types, enums, immutable-by-default variables, option/result types, various other goodies and the syntax for for loops actually makes sense. It’s a shame that the compiler is quite buggy, but hopefully that’s going to improve.
I know V for over promising in the past, a quick scan over the syntax really looks like how Go should've designed (can't really blame Go early design decisions, for me was not until Rust that I learned about the good side of functional programming languages, before that I didn't knew they ever existed.
But Go was not trying to be functional, but more general-purpose ease of use. V (Vlang), moves a bit closer to functional, while keeping the ease of use of its Go heritage. Rust builds on top of its OCaml heritage, however, this creates a lot of confusion because of the verbosity and syntax differences from what people are used to with other C-like languages.
Definitely, V (Vlang) is in the trajectory of a better Go or Go and C alternative. When you read V (Vlang), you can see how it's improving upon or advancing various Go concepts, while being C-like. Rust, because of the OCaml heritage, is a bit both odd and unique. It's positioned as a C/C++ replacement or alternative, but there is no easy recognition or transition from those languages. Possibly why there is a lot of friction and controversy surrounding Rust, as people haven't familiarized themselves with (or don't want to) with OCaml and that family of languages.
Go is simple, and revolutionized the design of language-supporting tooling with its all-in-one compiler, dependency manager, formatter, test, etc. C# has a long history of baffling complexity in terms of tooling and ecosystem. Maybe things are improving these days but there’s still a very long ways to go.
For illustration, when I google “C# install”, the top hit is “Install .Net on Windows” from Microsoft, a W3CSchools URL, and then a plethora of YouTube videos. The Microsoft link has several tables listing various different packages and whatnot. Is .Net the same thing as C#? I’m on a Mac anyways so it’s useless. I am stumped on how to even use this language before I get started.
When I Google “go install” the top hit is a page with simple instructions for each operating system (Windows, Linux, Mac. No tables to read or oracles to consult.
I’m not a go language enjoyer, but I do think the tooling around the language is an excellent high water mark for developer experience that’s had a lot of influence on programming language space. Rust/cargo, gleam, the dotnet CLI you mention
When I said “I’m on a Mac anyways so it’s useless” I’m referring to the results I get googling for “C# install” instructions, not the language itself. I don’t know why it’s so hard to find https://dotnet.microsoft.com/en-us/download/dotnet/9.0 or why that windows specific page has so many different thingies listed in all its tabes.
Specifically the map type. Unlike some Map implementations in Java, maps in Go are not ordered and there is no way to order them unless you want to keep a slice representing the order of the keys. Keys in these maps must be comparable, which means that unlike Java, you can't just implement equals() and hashCode() to make your type comparable. Maps and slices are not comparable, so if you use either in a struct, that struct is no longer comparable. Since map keys must be comparable, you'll have to convert your struct to a comparable type like a string if you want to use it as a key to a map.
You can always look for a third party library for more data structures, but the last time I looked those libraries didn't support generics yet.
Sometimes you'll find a standard library function that takes an interface{} and might panic if you give it a value it doesn't like. This is especially the case for library functions that want to operate on slices. Slices can be generic, but you'll still find many functions that accept interface{}, so you're left with library functions and third party libraries that take any as the type of an argument and expect you to read the documentation so that you don't accidentally pass the wrong type.
interface{}s are comparable sometimes, and if you compare two types that aren't actually comparable, you'll get a panic.