Ask HN: What do you like/dislike about Golang?
What I like:
- Ecosystem. Very easy to install and use libraries.
- Can compile programs to single executables easily.
- Easy to work with (Very productive) while also running fast.
What I disliked:
- The syntax but I'm starting to like it. (It seems that Go's syntax is an acquired taste for me)
110 comments
[ 3.9 ms ] story [ 187 ms ] threadGo and Typescript are now my go to for everything I think.
It's to the point where I will only be using golang for backends moving forward.
The tooling, including the built-in testing package and test tool, and go doc. Also vet and fmt, if for no other reason than it precludes most arguments over braces and tabs vs spaces. I don't have cause to use it much, but pprof is a dream.
I like the idea of the go module system, and the fact that dependency management is part of the language ecosystem. It's still a bit clunky, especially if you end up, like I did, stuck with a system that has lots of libraries with poorly-thought out dependency relationships.
interfaces, especially single-method ones. But also the fact that your code doesn't have to declare it's implementing an interface, and has no dependencies on the interface declaration itself.
Dislike: - Pointers -- maybe I am still getting the hang of it. But I feel like pointers throw off a lot of beginner programmers. Would love some practical advice here
Stick with pass-by-value and non-pointer receivers. Use pointers only if you have to.
Yes, they literally chose to subject themselves to runtime panics to silence the compiler.
Like everything else.
Not every program has to be 100% safe and delivering 2M req/s. Sometimes you just want to build a program once and ship it.
What other language has that? Go isn't perfect though. It's startup sequence is long, the API towards C (and other languages) is quite bad, and if you build a fibonacci program and compare it with C it's 3x slower despite requiring no allocations whatsoever.
In the comment I replied to you, I'm just stating that you're bringing up something that's not in discussion.
But I didn't even make the assumption that I was right on that understanding. I simply asked for more detail. Which was so that either I would learn something new, or the parent would.
Where do they say that it being part of the standard library is unique? I don't even see the words "standard library" mentioned.
EDIT: Look, this back and forth seems kind of pointless. This started with a statement about Go being unique for being able to be statically linked and talk HTTPS. If a language can be linked to compiled libraries, static or not, it's surely able to at least use libcurl, so I and the other commenter you replied to took it as Go is unique for being able to link statically. I assumed they meant, ignoring C/C++, static linking is rare, which I found interesting. I found other languages and asked for clarification. That's all.
I do not agree that it is incumbent upon a speaker to anticipate the listeners knowledge, so I do not think it is a reasonable expectation that every qualifier be included. It's simply not practical. But I do think this is an interesting conversation you bring up.
To be sure you might write such a program in assembly, but you’d be doing it for fun or aesthetics. No engineering manager would commission such a thing.
Though of course this is inviting the trading maniacs to tell stories of hyper optimized clients…
What do you mean? C, C++, D, Rust, Haskell, and Common Lisp seem to be capable of static linking. (Admittedly Common Lisp seems to require a fork of SBCL that was written about last year, but still)
Seems like a lot of replies are glossing over that clause. How many of those other languages can do that in a fully static binary that runs on (almost) any system that can read and launch that executable?
The fact https is included in the standard library means that you can give a new user a hello world tutorial that includes producing a web server. It's a huge boon to productivity in a programming language to have a default path for such libraries.
I also work in C++, and it is infuriating the amount of time that must be spent sorting out the correct libraries for all the various aspects of an application one is not inclined to write themselves. People who don't fully grok the Go ecosystem overlook this cost when they claim that you can do the same thing in some other language. What they are missing in the subtext is the fundamental quality of life improvement.
I'm not just taking it in isolation for no reason. If you have static linking, you basically have HTTPS by consequence.
> The fact https is included in the standard library means that you can give a new user a hello world tutorial that includes producing a web server. It's a huge boon to productivity in a programming language to have a default path for such libraries.
I think you're taking our discussion as if it were about if the language is cool or not. I never argued against that.
Can you clarify what you mean by this because to me there is literally nothing about static linking that implies https as a consequence. The point about Go is centrally not that it uniquely has access to an https library.
The point is that it is included. This may not at first appear all that noteworthy, but this is a substantive quality of life improvement. The standard library not only provides a vary large set of common functionality it is packaged with the distribution, works on all the platforms supported by Go without user intervention, and is bound in lock step to the release version of the compiler.
I don't believe this can be said of the other languages. I know that C, C++ and Rust do not include https in their standard libraries, so while they can be made to compile statically and use a library that provides https functionality, those are additional steps that must be taken by the developer, and it is the responsibility of the developer to choose the correct source and version of the https library to use. This will also include understanding and setting any additional compiler flags that the library may require, setting any optional defines or other library configuration settings and making the appropriate changes for every platform they wish to build for.
Go requires none of this.
2. The "go" keyword (and the whole goroutine system underneath it). Threads are clunky, somewhat unportable and generally unscalable. Goroutines just feel right.
3. Ability to use multiple major versions of a library. Diamond dependencies are an unsolved problem in many languages I've worked with, but with Go you don't ever even think about them.
4. Implicit interfaces. Having to write "implements X" just to be able to use a type always felt wrong. Implicit interfaces just make sense.
5. The gopher mascot. I just love that little guy.
3. Implicit interfaces. I'm in love with this idea when I learned of it. however it wasnt obvious that this should be a thing. if you learned interface from other languages first
'implements X', is a clear statement that yes, this method is made just for that specific interface.
But this is just academic. I've never actually hit a situation yet where something just happens to implement an interface by chance, and does something completely unexpected.
``` type Doer interface { Do() error }
type CanDoer struct{}
var _ Doer = &CanDoer{} // This ```
If CanDoer does not implement Doer, it will fail to compile, and you can quickly see what you need to do to fix.
For example, I could create a type with a `Read([]byte) (int, error)` method that doesn't conform to the `io.Reader` expected behavior (e.g. reads from the byte array and returns an int if byte array contains a numeric string), yet I'd be able to pass it as an argument to a function that accepts `io.Reader` - the type system won't stop me, but the behavior will be broken.
However, in practice, writing erroneous implementations of common interfaces is uncommon. Naming conventions are really important in Go, so the only way to abuse type system in this way is to abuse the naming conventions, which is already considered bad practice in any sane programming language.
Typical debugging problem in dynamic languages.
To be honest, it's pretty hard to implement an interface by accident. You'd have to specifically implement methods with specific names and type signatures.
If method names + type signatures don't intuitively specify how should implementation behave, then it's either a bad interface or a trivial one.
- Easy to deploy
- Short learning curve
- goroutines
What I don't like:
- c bindings are not the best
- Error handling code gets old, but at least it's always consistent and never magical
A downside is Google's approach to handling modules: https://drewdevault.com/2021/08/06/goproxy-breaks-go.html
As to the criticism, if a public module isn't playing well with the default proxy, it's a good sign I don't want to use it to begin with.
Yeah, but defaults matter. That's one of Go's big selling points, usually.
> As to the criticism, if a public module isn't playing well with the default proxy, it's a good sign I don't want to use it to begin with.
The concern raised here is more that things only work because the proxy masks problems. Also alluded to is that said default proxy is kind of awful to its upstreams, unnecessarily pulling things and burning bandwidth.
But making the modules available in the face of upstream breakage is a point of the proxy. I can still build when GitHub is down. I can still get the version of the source I expected when upstream rebases or retags. Those are reasonable defaults. And with one env var I can also turn that off and check that the upstream is still as I expect.
> Also alluded to is that said default proxy is kind of awful to its upstreams
This is debatable; sr.ht seems to want to host a lot of go repos but not have people pull a lot of go repos. I'm not sure that's a case worth catering too.
Go is really nice to read (once you get used to the fact that half the lines of code in any function are for error handling) unless it uses a lot of channels. Then you need to take great care to understand every <-.
Interesting. Most complain about languages with async/await and say they want goroutines. What is it about async/await that you prefer over Go's approach?
(Do we add an error field to the struct we send through a channel? Do we close the channel on error, or only on success? Do we make a separate channel to send errors on - and if we close that one it means for sure that there were no errors? If we select on the two dependent channels, do we have to make sure that they are in different sequential selects, so that we don't go out-of-order or deadlock?)
Promise.all is trivial:
Promise.race is a little longer: What Go doesn't do very well compared to promises is composing; composing channels, especially with error returns, is usually tricky. But async/await are also bad at this. Passing around the actual promise/futures objects is the best way, which is for some reason discouraged today in favor of await/async noise in most cases.Dislike: still no easy way to dynamically create mocks -maybe some brave soul will try to solve it after generics :)- I don't like code generation or manual mocking, we have a large codebase, 1000+ file and so much noise in the repo because of generated mocks. And yes we are fan of unit tests and aiming 100% coverage on business logic.
It is a nice language had it been released in the mid-90's, following the footsteps of Oberon and Limbo.
What I dislike,
Being designed a decade later ignoring everything that happened in mainstream computing since Oberon and Limbo came to be, then adopting features that weren't properly backed in from the get go.
For one, I think it's mind blowing that they didn't have a good system for dependency management in place from the get-go. Things are pretty good now with go modules but it's like they never even heard of languages like Python and the unholy mess that package management was (and still is to some extent) with Python.
The const/type declaration dance to this day, to declare enumerations, even Algol could do better.
Error handling based on comparing strings, to find out the real cause.
I'm not a fan of Go, at all, but credit where due: they did make this slightly better with error wrapping (https://pkg.go.dev/errors#Is). Of course, this doesn't work unless errors are wrapped, instead of blindly concatenated, forcing you to do string matching more often than not. :)
Dislike: awkward error handling and testing / mocking
- Community. Pragmatic, high signal to noise ratio in online resources, little magic, sympathetic to the needs of production operations. Consider for example httptrace [0]. This kind of introspection isn't even normally possible in many languages and their library ecosystems. In Go world, people care about this stuff.
- Standard library. High quality and flexible packages for stuff like web servers and clients, TLS certificate manipulation, SQL, various data encodings, etc.
- AST package, strong first-party support for manipulating Go source files.
- Table driven testing norm is very cool in certain circumstances.
Con:
- Verbosity. Something like making an HTTP request is ~7 different operations, each with their own error branches.
- The tedium of meeting coverage mandates. Having to code-generate tons of mocks and type out very elaborate test tables for what feels more like busywork than genuine confidence-building. Partly this is my employer's fault for having high coverage standards though.
- Despite its apparent simplicity, there are significant footguns lying around slices, loop variable captures, channel deadlocks, etc.
- The Go community's solution to a lot of language shortcomings (verbosity, type system, etc) is code generation. For example, we have an internal framework for parallel task graph execution, which is much safer and cleaner than manually managing channels and goroutines. But this kind of thing cannot be expressed as a regular library like it would be in other languages.
- Despite the prevalance of code generation, Go does not quite have a macro system, leading to third-party build systems like Bazel in companies that do a lot of code generation with their Go. This then leads to rough edges with the other tools' handling of "de-materialized" generated code and the need for stuff like GOPACKAGESDRIVER for proper editor support. Editor and debugger support for generated code, the way things stand, can be rough.
- Certain syntax rules such as trailing commas and unused variables/imports (especially when commenting stuff out temporarily to debug something!) can get on my nerves.
[0] https://pkg.go.dev/net/http/httptrace@go1.19.3
Like:
- Standard Tooling: formatting coverage, dependencies, BIN install, versioning, vendoring all done in Go CLI
- Opinionated, minimal design: often I feel like there are fewer ways to do things in Go than in other languages.
- Readability: fewer operators and minimal language design make it feel easier to ramp up and read most go programs
Dislike:
- Hard to master: some elements are very unintuitive coming from other languages (interfaces). I feel like a lot of content on go.dev is out of date (Effective Go). The Google Style guide is helping to light a path and when they publish Go Tips I think it will get better.
- Too Minimal: in some cases it feels like Go went too far in not building language features (no set, use map[$TYPE]bool instead or map[$TYPE]struct{} for more efficiency but less readable imo)
- Ecosystem
- Static linking
- goroutines
- small and light runtime
- fast compilation speed
Dislike:
- missing shorthand lambda functions, making functional programming very painful
- typed nils
- billion dollar mistake (lack of non-nullable types)
- implicit/automatic copying on value types. Value types as a whole need to be re-thought.
- too much error handling boilerplate
- unused variables are compiler errors
- struct init not exhaustive
- Compiles to a static binary that's relatively small compared to something like .NET AOT.
- Structural typing.
- No semicolons.
Dislike:
- Lack of exceptions. I can understand wanting to avoid exceptions for safety critical software like avionics, but for most application programming, requiring manual error handling for every function call is cumbersome and unnecessary.
- Too verbose / lacking features. For example, there's not a short syntax for lambda expressions, like the => operator in C# or JavaScript.
- Public / private access modifiers at the package level rather than the class level (there are no classes).
- Language development is driven primarily by Google, who has a history of killing products.
Dislike: Error handling feels extremely verbose. I like the design choice to return errors explicitly, but it feels like there needs to be a return-if-error syntactic sugar introduced to avoid the endless if err != nil ... return err peppered everywhere.
I don't have much experience with go, but that's not at all my experience. I had the compiler panic on me for daring to run "go get", as described here: https://github.com/golang/go/issues/47979
This issue has now been closed, but there are several others open with the same symptoms. The response by a golang maintainer was:
> It seems likely that there is still a bug here, but the code is complex enough that we're unlikely to find it by just reading through the code.
Anecdotally, i've tried to fork the imported module to remove some dependencies i was not using, and failed miserably, as explained here: https://github.com/ooni/probe-cli/pull/986#issuecomment-1327...
I have experience with C/Rust import systems and i can say with 100% confidence that golang's module system is far from amazing.
-The go AST package was a pleasure to use
Dislike:
- Boilerplate error handling
- Excessive verbosity
- Multiple return not being of a type
- Duck Typed interfaces and the empty interface
- Go generate that makes verbosity worse
- Case-sensitive namespacing that leads to hunts for all the
instances
- Namespacing in modules that leads very large files
- Shadowing rules that let you shoot yourself in the
- Rules around =, := and var
- Slice tricks and for loops try to encompass iteration instead of something like list comprehensions
- Nullability and default values
- Short variable names to the point of being meaningless even in the standard library
- The go community's seeming insistence on a singular or idiomatic way to do things
- The standard library is missing basics
Edit: Added some formatting.
I wish channels and goroutines were more strict, so that you could have some basic assurance no race conditions can be present.
Defer is very convenient, which is a problem, as it is a source of bugs. It's usually used to ensure file-like objects get closed, but generally those return important errors when closed. A simple "defer file.Close()" will silently ignore the errors generated.
- Big standard lib, including SQL base classes, allowing very consistent DB code. No 6 different ORMs and slightly different DB code for every DB type. Ahem Rust.
- Cross-compiling is easy and consistent
Dislikes:
- Error handling. Done to death, but it direly needs some syntax sugar like Rust's `?` operator. Seems like 3/4 of any function that does anything significant is `if err` branches.
- Nullabilty and the way references work feels like a footgun. Rust feels more clear about what's happening when. Not sure there's much to be done about it now though.
What I don't like is that Rust is also very intriguing to me :D