Hacker News new | past | comments | ask | show | jobs | submit login
Lies we tell ourselves to keep using Golang (fasterthanli.me)
130 points by danslo on Dec 30, 2022 | hide | past | favorite | 147 comments



Having shipped a little golang code (back in ~2017) and having watched things evolve, my sense is this:

- Golang doesn't fit comfortably into the spectrum of "low level & fast v.s. high level and easy" languages, largely because it does go in different directions in many ways (see: critiques on their garbage collector, type system, etc).

- Smart people are happy with Golang, but the fact that it makes different choices means that people regularly discover to their sorrow that the language isn't quite what they expected.

- In general, most of the "go was wrong for us" stories seem to come from companies that use it as a side language. Teams where Golang is their main workhorse generally seem at peace with its choices.

To me, it means I have no doubt you can build a company on Go - but maybe build that experimental high-performance replacement for a python service in Rust.


"Teams where Golang is their workhorse" probably made that choice a long time ago and have a lot of legacy code in the language. Staying with Go for the most part is just the sensible thing to do in that situation.


The article spends an inordinate amount of time to delve into meta arguments about groupthink and elitism and juniors vs. seniors which is interesting enough, but I find the meat of the argument a bit lacking.

Go has some warts, but they're all things that make sense if you know some basic concepts about go (as in you can derive them from knowing how go ticks), versus thousands of random caveats in the core library of other languages. They seem to be most upset about allocation, but it is in fact pretty simple. Types that can be nil are nil upon initialisation, and everything else is a zero value (by virtue of not being a pointer). C isn't actually different here. The only thing I'd expect to surprise a C programmer here is that strings aren't pointers by default, despite being variable length.

And I'm not sure what their point about CGO is. People always say to avoid it because you leave the Go world behind and because performance could be better, but it is still a legitimate way to achieve FFI. The SQLite package uses it and nobody recommends against using that either. It just comes with a "more complexity if you add this" disclaimer.


> Go has some warts, but they're all things that make sense if you know some basic concepts about go

That's exactly the problem. I don't want to spend my time learning the myriad quirks of a language. I want to spend my time building my program. As someone who is an "advanced beginner" at Go, I assure you that just knowing "some basic concepts" is not enough to avoid Go's pitfalls and footguns.

I don't want to work in a language where the solution to all these things is "just be careful when writing code". I'm fallible. Compilers, less so. The compiler should be doing this work for me.


> I don't want to spend my time learning the myriad quirks of a language.

That is just it. Go's quirks are mostly a result of its consistency, not a result of a lack of consistency.

> I assure you that just knowing "some basic concepts" is not enough to avoid Go's pitfalls and footguns.

It seems that Go has two somewhat contradictory goals: consistency and similarity to popular yet inconsistent languages. This seems to result in a lot of people who can generally use the language, but haven't learned the language model because the language doesn't require that in order to get started. This seems to frustrate a lot of people and prevent them from ever learning the language model. Languages which appear completely different from popular languages don't seem to suffer this issue because people tend to learn the language model up front.

Go really only has one major source of inconsistency and that is the late addition of type parameterization. The built-in parameterized types are special, but still generally follow the same rules as other types with a few exceptions. The main thing you need to know about slices, maps, and interfaces is what they actually are. Once you know that, the normal rules apply. Channels are a different beast though.


How can you honestly claim that the language is consistent when it didn’t use to be able to express a map without supporting it on a language level (instead of only being a lib)? A properly consistent language would be one where you could reasonably come up with how to use anything, including such a basic data type just from simple primitives. Go is very far from that.

Have a look at the excellent “Growing a language” talk by Guy Steele where he goes into great detail on what would a truly consistent language entail.


"Being able to solve every programming problem" is not a necessary prerequisite for consistency. If that were true, SQL would be inconsistent because it cannot interface with libgl in order to render 3D graphics. That is clearly nonsense.


Was this comment meant for me?


> The SQLite package uses it and nobody recommends against using that either.

There is the modernc.org/sqlite package that doesn't use cgo. Currently the performance is lower, but it's an option.


Something that annoys me:

- Go's approach to normal error handling assumes that correctness and proper error handling are paramount: every error must be handled explicitly, and every control flow path must be made obvious, no matter how much verbosity this creates.

- Go's approach to panic handling assumes that correctness and proper error handling don't matter: a function can halt at any given point, panics are easy to trigger by accident (e.g. methods with nil receivers), handling them explicitly is usually discouraged, and they tend to leave values in intermediate and unexpected states (unless you use "defer" very carefully and consistently).

Rust partially resolves this issue by preventing many causes of panics, by using methods like "lock poisoning" to avoid leaving shared values in unexpected states, and by having proper destructors.

Go's approach (just crash everything) makes it easy for one error to completely bring down an application. Handling panics leads to values being left in inconsistent states with operations half-completed.


One of the difficulties here is that you can’t actually turn every fault into an error return. Yes, Rust “partially” resolves the issue, but if it’s not null pointers, it’s out of bounds array accesses or something like that.

The reason I say you can’t turn every fault into an error return is because an unexpected infinite loop is also a type of fault. If you call some function that has bugs in it, then there’s a chance that the function won’t return. “Won’t return” may mean that it panics, deadlocks, loops forever, or loops for so long it might as well be forever. In Haskell, all of these different behaviors are lumped in together as “bottom”, and bottom is a value which has well-defined semantics even though it covers all these different cases. There’s a whole debate in the Haskell community about whether functions should be total. A total function doesn’t return bottom unless bottom was an argument—in Go/Rust terms, a total function does not panic and does not infinitely loop.

My take—as long as you think “this function might not return because it has a bug in it”, there’s not a good reason to prohibit panic(). The panic() functionality is a more controlled, flexible way for a function to not return.

I think you could write a whole article on when to use recover() in Go. The idea that you should never panic is a bit of a hopeless dream—if that’s the kind of correctness you want, then it sounds like you want some kind of formal verification, which can be done but not in Go. The idea that you should never recover() is too severe. Yes, you can find code that leaves your program in an inconsistent state after a panic(), but in practice I’d say that these problems are relatively rare. You can also use panic/recover to simplify your code in certain ways. I’ve used panic/recover to write parsers or deserialization code, where you just use a panic() to return an error from the top-level parser/deserializer, which catches it with a recover().

I’d also say that it’s relatively normal to recover() inside your request handler for network services. You could weigh the risk of panic/recover leaving your application server in an unexpected state against the risk of getting a denial of service from panic taking down the whole app.


Panics in Rust are for logic errors that can't be meaningfully recovered from. You can easily convert an error state into a panic, by using .unwrap() or .expect(), or pass the error back to the caller via the '?' syntax. Rust does have a "recover"-like facility that can catch a panic, but it's intended for exceptional use; it also has no effect when panics are configured (at the whole-program level) to abort the program immediately.


Yes, that matches my understanding. In Go, panics can also be used for recoverable errors, if you so choose. In Rust you can catch_unwind() which is similar to Go’s recover(), and the big difference is that (1) it may not work depending on how your project is configured, and (2) its use is very strongly discouraged.


In theory, Go is the same way, but Go does a lot less to prevent panics, limit them to very specific circumstances, mitigate their effects, or limit unexpected failure modes.


> Yes, Rust “partially” resolves the issue

Which is a pretty big advantage!


Doesn’t Go also partially solve the issue?

I write both Go and Rust code, and I don’t feel like there’s a huge advantage to one side or the other here. Maybe I don’t understand what point you are making.


Whether it's a big or small advantage is an empirical question. How often do panics happen otherwise, and how bad are they? It's going to depend on the system.


> panics are easy to trigger (e.g. methods with nil receivers)

No, methods nil receivers in Go don't trigger panics (if they are not dereferenced).

> every error must be handled explicitly, and every control flow path must be made obvious, no matter how much verbosity this creates.

Is this, bad? It is just the recommended way. You are not required to implement your code in this way.


> No, methods nil receivers in Go don't trigger panics (if they are not dereferenced).

Yes, but in practice most methods do dereference their receivers. Given that, you have two choices:

1. Check for nil receivers explicitly in every method. This is considered unidiomatic and libraries rarely do this.

2. Don't check for nil receivers, and have your method panic on the first dereference with no explicit check. Then, most of your methods can halt partway through in unexpected (and usually undocumented) ways, unless you pay very close attention to this failure mode.

Furthermore, such panics can occur far down the call stack, making it non-obvious from the stack trace where the error is.

Also, this means that, if you upgrade your library so that a method now dereferences its receiver, your library is suddenly no longer backwards-compatible.


In my honest opinion, you describe a theory problem not happening in production practice.

It is the NORMAL behavior to let it panic when a nil receiver is dereferenced. DON'T try to check it!


To the second point: I think the issue is that you don't want to handle the errors in a function, but just propagate it, you still need to write error handling code, whereas in languages with exceptions you can just not do anything and let the exception propagate. Hence, the extra verbosity. Not checking for error is not an option in go.


What is the way you recommend?


The only way to do it in go is to always handle errors, even if you just want to pass them through. It’s one of the downsides of the language. My recommended way is to use a language with exceptions. They have downsides too, of course. It’s always a trade off, but I personally think the upsides of exceptions outweigh the downsides. At least the better implementations of them do.


Go supports exceptions: panics.


That's not a particularly nice implementation of exceptions and all libraries treat panics as invariant checks, things that should never happen. So, if you want to treat expected errors (eg. file not found) using panic you simple won't be able to go very far, unless you create some functions to convert all errors into panics. But, even then, panic is not great. They are not typed, so you never know what panics could be raised from a function and using a single deferred function that has to handle all errors whereve it is you want to handle them is quite awkward. Obviously, the reason for those design choices is that panics are really not exceptions.


Yes, panics are not totally the same as exceptions. You can view them as simpler and less powerful exceptions. The Go culture is almost different from Java form every perspective. Go prefers explicit errors over exceptions.


Exactly, that's the criticism. Many find exceptions better than explicitly handling errors, even when you just want to propagate them.


Many others have contrary opinions. So this is just a subjective preference. If you like using exceptions, just don't choose Go. It is useless to criticize it. Go will not change for the criticism, and it is unable to change at this time point.


I wasn't suggesting go changes. I was just replying to

> > every error must be handled explicitly, and every control flow path must be made obvious, no matter how much verbosity this creates.

> Is this, bad? It is just the recommended way. You are not required to implement your code in this way.

Pointing out that it is not the recommended way, it is really the only sane way to do it in go. And, in my opinion, that's bad design.


Loop back: you can use the panic/recover way.

> ... that's bad design.

This is a subjective opinion. Many people don't think so.


Why would you have a method that doesn’t touch the receiver? Refactor it into a function.


Dereferencing isn't the only way to touch a pointer:

    func (o *myObject) isNil() bool {
        return o == nil
    }


Right, but if you’re doing a nil check, you obviously won’t be worried about nil deferences.


Either to implement an interface, or because it branches to check if the value is nil before dereferencing it


Re: the second point, see my other comment.


It's not fully general, but crashing and letting the outer system handle the restart can make sense for servers. The system needs to handle restarts anyway.

It usually works okay unless there's a "query of death" causing repeated restarts.

Similarly for command line apps.


> The very reason I don't consider Go a language "suitable for beginners" is precisely that its compiler accepts so much code that is very clearly wrong.

This struck me as a really insightful thing to say. I often talk about how I like languages with strong type systems, and how I like using those type systems as fully as my brain thinks is reasonable to do so, because when the compiler finishes with no error, I have much more confidence that what I wrote is correct.

But I never really thought about it from the other side: being beginner friendly doesn't just mean "you can pick up the syntax in an afternoon". It should mean that the compiler saves you from beginner mistakes. As someone who only occasionally uses Go, I'd consider myself an "advanced beginner" at the language. And I remember many of the times I wrote code that seemed correct, and the compiler accepted, but was wrong because of Go's quirks.

That alone is a reason to disqualify Go for me as a language to get serious work done in.


I find the opposite to be true about junior/senior. junior programmers accepts all kinds of awful things because they assume there is a good reason. senior programmers are pretty deeply aware that the whole jenga pile is suspect.


I see junior programmers come in and have one of two reactions to the code base, or both reactions at the same time:

- “This code is awful, it’s full of all sorts of hacks, spaghetti code, bad practices, etc.”

- “This code must be fine, because the people working on it are smart, and if I think there’s something wrong, it must be because I don’t know the reasons for it.”

The problem is that junior programmers don’t have a good intuitive sense to figure out which of those reactions is the correct one. Sometimes you get junior programmers who are rightly horrified about the state your code is in, and want to fix it. Sometimes you get junior programmers who think the code is awful just because it doesn’t look like the good code they saw when they were in school, or because they don’t know what good production code looks like (which is often in flux).

The responsibility of the senior developer is to protect the junior developer’s opinions about the code like they’re a flickering candle that could get blown out at any moment. The junior developer will either hold onto those opinions and try changing the code, or will decide that they’re wrong—and the key skill you want to cultivate is the ability to make the correct decision.

It’s easy for a senior developer to simply tell the junior developers what the correct answer is, which is in this metaphor, means taking the candle away.


I was lucky to learn this a decade ago when I was an intern; except it came from the electrical engineering side of the department (a department who produced products that were seen as the gold standard for that part of the medical device industry)

Manager (who was a world class electrical engineer): "why did you reuse this?"

Friend (who was also an intern): "I assumed ${staffEngineer} knew what they were doing and it would be fine to just place it in this design"

Manager: "never assume that anyone knows what they're doing here or at any other organization."


Ha that last is the truest advice ever. I trust some people to end up at good decisions, but in the flux, there are many silly things tried, thought, and proposed. Don't even trust your past self too much.


That's a great way to have infinite Not Invented Here (NIH) syndrome.


I think Not Invented Here syndrome is generally foolish and quite arrogant, but Not _Implemented_ Here syndrome can be a reasonable default. You rarely truly understand something unless you've implemented it yourself. You can draw inspiration from other sources of knowledge and in the process of implementation, you may decide that the source is better than what you would come up with. You might also discover that you can improve on the source, at least in some dimension. Your implementation could be as simple as a wrapper around the api of an existing library (or a DSL for using it). But after you've written the wrappers, you may find that it isn't too bad to just implement the darn thing yourself (probably don't do this with crypto!). I find it is generally a mistake to just bring in dependencies and directly build on top of them because then you become vulnerable to bit rot and vulnerabilities (or just needless complexity the use cases of other library users that are irrelevant to you) introduced upstream no matter how conscientious the upstream authors are.


> Not invented here (NIH) is the tendency to avoid using or buying products, research, standards, or knowledge from external origins.

It doesn't have to be actually invented, you can just build everything yourself.

Point is take that mindset ("Never assume anyone else in this organization knows anything"), quickly becomes never trust anyone else, and degenerates to - we'll need to rewrite the gluons (luckily people give up after they spend all their free time).

I prefer the Chesterton's fence. If you don't know what it does you can't remove it.

> I find it is generally a mistake to just bring in dependencies

I don't agree.

It's a pick your poison situation. Use dependency and have a centralized source of mistakes or distribute mistakes, e.g. billions of xml parsers suffering the same issue.

In my experience cautious use of dependencies is preferred, but I would take a well known devil. Over a homebrewed one.


Every failed project I've ever been a part of has been a direct result of poor choice of dependencies. My experience is surely not universal, but nor is it rare.


Only if you’re unaware that “anyone” also includes yourself.


People always think they are the one exception.


That is more cargo cult behavior than NIH


What really makes someone a senior engineer is having made enough bad choices to realize which bad choice they're about to make.

And it's pretty much always a bad choice. It might be a little bad - like if you're using PostgreSQL, you'll need TestContainers for your integration tests, which can slow the build - or it can be a lot bad - like if you're using Oracle, a horde of demonic spirits now know your True Name and are laying plans to extract the most value for their Dark Lord - but there's always tradeoffs.

Senior engineers are just better able to list and weigh those tradeoffs, and maybe hedge against some of the badness through careful architecture.


Agree, and the inverse problem is also prevalent, as we all know: someone without experience looks at a system, declares it to be shit while ignoring all the edge cases and design constraints that went into it, and insists they can do better by starting from scratch.


At a company I worked at, one of the VPs secured his son a "Senior Developer" role fresh out of a boot camp. His son was assigned to a high-visibility project that had been in development for several months and immediately started trashing it for using SQL instead of NoSQL, amongst many other reasons. He drew up some fancy diagrams and managed to convince people that the only way for the project to 'scale' was to do a rewrite from scratch with a new shiny tech stack. Several months later and they were still struggling to deliver a "Hello World".


Those who don't understand system's history/structure/constraints are doomed to re-implement it... Badly.

See: C++/Java OOP implementation.


My experience as well. Junior programmers tends to just copy whatever is “hot” at the moment. Without having the experience needed to truly evaluate their choices.

And when the ship sinks under their feet they somethings don’t accept or realise that the ship is sinking because of choices they made. However the smart ones will learn and make better choices in the future.


> junior programmers accepts all kinds of awful things...senior programmers are pretty deeply aware that the whole jenga pile is suspect.

Interesting.

How does that fit into the success of Node.js?

Even the author of Node.js accepts that it is dreadful. Yet it underpins so much current technology. Even in the Rust world.


Senior programmers are a minority, due to the software industry’s growth trajectory.


I didn't imply there was some well-built, useful and simple reality we could all just switch to. its all broken to some degree. not at all surprised that something inherently flawed could be useful.


Go criticism is always focused on the language itself, which is admittedly non-optimal. But it's non-optimal in the direction of missing features. Contrast that with most languages which are non-optimal in the direction of having too many features which are poorly designed.

Go's position on the simplicity vs feature set continuum is perhaps the least interesting reason it's popular. There are a myriad of other reasons for its success:

- It spits out static binaries with minimal fuss.

- The import statement maps strings to symbols. No complicated module system to learn. The strings are resolved to something like URLs, but that's not part of the language. It's very easy to see code on GitHub and import it.

- Packages are just directories.

- No new package namespace with squatters and poor infrastructure. Go packages, in practice, are named with DNS. You know, the namespace that already exists and everything else has standardized on.

- Rejection of complicated version selection. Go almost went down the npm route, there was even an "official experiment" that looked much like other package managers. Then the Go leadership stepped in and sorted things out. https://research.swtch.com/vgo-mvs

Go is set up to be the language of an ecosystem in a way that most other languages are not. If you are a language designer: take Go's import statement, packages, and modules, and just copy them. You aren't going to do better, and you probably weren't trying to innovate in that area anyway.


> The import statement maps strings to symbols. No complicated module system to learn. The strings are resolved to something like URLs, but that's not part of the language. It's very easy to see code on GitHub and import it.

This is for sure untrue, unless one is already steeped in the golang ecosystem.

  $ cat go.mod
  requires (
    github.com/pulumi/pulumi/sdk/v3 v3.43.1
  )
oh, cool, I can just navigate to https://github.com/pulumi/pulumi/tree/v3.43.1/sdk/v3 to view the source ... oh, :sad-trombone: it's 404. Well, why is that?

  $ curl https://github.com/pulumi/pulumi/blob/v3.43.1/sdk/go.mod | head 1
  module github.com/pulumi/pulumi/sdk/v3
Oh, because the "module" line is decoupled from the apparent URL. Then there's this `gopkg.in/yaml.v2` nonsense, as described by this: https://go.dev/src/cmd/go/internal/help/helpdoc.go#L251

Meaning navigating to https://gopkg.in/yaml.v2 gets one thing, but https://gopkg.in/yaml.v2?go-get=1 produces another and only a view-source of the latter shows what golang is going to use. Yes, I'm aware that out of the kindness of the author's heart the browser version does link to the alleged source repo, but alleged is the key part of that


The Go module system has some cruft that could only really be removed in Go 2. The gopkg stuff was an attempt to bring the fragmented ecosystem along to the current solution. It's not perfect, but the important stuff (like version selection) the go.sum file etc, is well designed.

You're quoting me talking about the Go import statement, and then shifting to talking about Go modules. The import statement, at the level of the language maps a string to a symbol. Packages are just directories. I meant to compare it to the more complicated package/module systems which are built into other languages like Python or Rust.

I said:

> It's very easy to see code on GitHub and import it.

Which I maintain is true. I usually just type the import line into my text editor and the tooling figures out the rest, or prompts me to "go get". Your example talks about going in the reverse direction, which I never claimed was easy. I don't think I've ever had to do that either. If I want to see the exact version of a library, the go tool has downloaded it to the local filesystem.


Plus very fast compile times.


> Go is a pretty good async runtime

I think the author is in many ways right, but has missed how important the runtime is to the average programmer.

At the time of release Go made it easy to write evented IO servers, in a way that Rust, despite its superior language design (IMHO), still hasn't. This is why people continue to use it.

Other examples are PHP, a car crash of a language, but suddenly it was easy to dynamically generate a webpage, or early versions of Java, widely derided for language design, but programmers were happy to trade the virtues of elegance and efficiency for the ease of the standard library, the GC, and portability.

Give the programmers the ability to do easily something they weren't able to do prior to using your language, and they'll use it, whether PL enthusiasts are happy about that or not.


This is also why NodeJS got so popular. Concurrent programming is made so damned easy in it, that of course it blew up in popularity.

Most folks just need concurrent code, not parallel code. NodeJS was able to provide that.


> PHP, a car crash of a language

That car crash of a language can imitate the general characteristics and typing style of any language and provide for using any pattern/anti-pattern in computer languages by just modifying its ini files to however you want it to work. That's why its 80% of the web.


Which is just as much of a curse, see lisps’ flexibility.


I am not a fan of async/await paradigms.

It is yet another example of making easy things easy.

It is not hard to write non blocking code. No harder than remembering to insert a keyword "await".

Event loops are very useful, I agree. I just do not see how a new syntax makes them "easier".


I've written a heaping pile of Go code over the last few years. I consider myself a fan, but not a fanatic. I tend to believe that Go's failings aren't any worse than the failings of most other languages, and for me, the drop-dead-simple cross-compiling and batteries-included standard library is well worth its warts.

That said, I found myself vehemently agreeing with the author re: how hostile Go is to FFI/etc. IMO, a network boundary is indeed the sanest way for most other things to communicate with Go code.

Re: Go on Windows: anecdotally, things seem a lot better than even a couple years ago, but there's still room for improvement for sure. It's baffling that the semantics of e.g. os.Rename() are so different between POSIX-y platforms and Windows when they don't have to be (Windows supports POSIX-style renaming semantics if you ask nicely). I ended up having to reimplement os.Rename() on Windows in terms of SetFileInformationByHandle() with the FILE_RENAME_POSIX_SEMANTICS and FILE_RENAME_REPLACE_IF_EXISTS flags set to get the behavior I was looking for.

Re: mutable state, I think these concerns are mostly overblown. These are problems we've been dealing with in the vast majority of mainstream languages for many years now, and there are plenty of strategies for dealing with it.

For example, if I want to pass an "object" by reference in Go but do not want it to be mutated, I'll wrap it in an interface with getter methods for its fields, but no setters or anything that can cause side effects. Sure, it's a bit more involved than tagging something with the `const` keyword in e.g. C++, but it's plenty effective.


> Re: mutable state, I think these concerns are mostly overblown. These are problems we've been dealing with in the vast majority of mainstream languages for many years now....

Yes.

Do not minimise this.

For very large complex systems immutable state is very helpful.

> pass an "object" by reference in Go but do not want it to be mutated, I'll wrap it in an interface

Making the point.

Horses for courses, but this is picking the course for this nag.


100% agreed -- if it wasn't clear, I wasn't trying to downplay the importance of immutable state. Just saying that it's perfectly achievable in Go with a small bit of effort.


I agree that structs should enforce using all fields or have provided some defaults at declaration, a result type would be nice, FFI is much harder than most other PLs I’ve worked with, and if your use case is a bad case for the GC, you’re in trouble.

At the same time, I really like go. No other AOT-compiled language I know of makes concurrency so easy: `go foo()`. It’s wonderfully simple and I love that using the language doesn’t cram my working memory. It feels like all the space is there for my problem. It isn’t the right tool for some jobs, and of course, you’re perfectly entitled to your opinion. But I think it’s a welcome tool in the toolbox.

I recently had to implement Raft in Go, and I don’t know of a better language for that. Feel free to inform me otherwise. The race condition checker, RPC libs, and simple concurrency let me focus on the algorithm implementation. The iteration speeds for me were super fast too since Go keeps compile times snappy.

These are all useful tradeoffs for me to keep in mind when deciding the right tool for the job. I wouldn’t consider these “lies” personally, but maybe I do have the wool pulled over my eyes :)


> I agree that structs should enforce using all fields

Nah, enforcement would be a problem if you're just say making a protocol decoder, you might "just" write struct as defined by API but not need to use all of its fields, or not use it inside the package (say package that decodes some JSON then just returns a struct with it)

I'd like go vet option for that tho as in some cases that would be useful.

> or have provided some defaults at declaration

Outright initializers would be nice vs New*() for any type that needs it and having developer to remember to call that


> And so they didn't. They didn't design a language. It sorta just "happened".

Maybe I'm wrong, but when I use Go it definitely feels designed: specifically, it feels designed to discourage the worst or most-alien-to-the-language's-intended-style dep in your dep tree from being too bad or too alien. That is, the design seems to me (and again, I could be wrong and this effect was in fact an accident) most concerned with keeping the quality and style-distribution of the language's ecosystem, or of a given codebase, on a nice, narrow, pointy curve rather than a flattish wide one, so much so that the authors would prefer an expert be a little annoyed with some part of the language to making doing the Wrong Thing too easy for less-adept developers (or for "experts").

If that was in fact unintentional, well, then my favorite quality of the language (and IMO the most interesting thing about it) was an accident, I suppose. And it's not like I've dug deep into the history of the language's development, so that may be the case for all I know.


Been a golang dev for a long time, although got out of it recently (I got very tired of it). Many contributions to k8s in the early days, etc.

Thing I find the most frustrating about golang is the community. It almost seems to be made of people who never used another programming language in their life. Defense of things like lack of generics, lack of a good collections library, lack of good error handling, lack of good dependency management, etc. It's so bizarre to me.

As a former Java dev, we all at least admitted the flaws of the language and came up with ways to deal with it (see Effective Java).


It's a little presumptuous to assume the teams and developers using Go are lying to themselves about Go vs. they've made the calculations and like the tradeoffs. Opinionated language and tooling plus good enough performance without a runtime may be worth more to them than the two (?? I lost count trying to find them in the article) issues the author brings up.


> It's a little presumptuous to assume the teams and developers using Go are lying to themselves about Go vs.....

I think it is a bit naive to think that these sorts of decisions are made by competent technical people.

In my experience these decisions are made by managers that have "Peter Principled" past their competence.


I was at Google the whole time Go was being developed. A friend of mine had two, count 'em, two quotes that pertain to it:

1. The answer to an unasked question

2. When your only tool is a hammer, every problem looks like a thumb

And one I just made up:

3. I had a problem, so I invented a new programming language. Now I have 769 problems

We could look at Rob Pike & the Bell Labs guys and imagine Stephen Stills saying "Hey, 'For What It's Worth' was the worst song Buffalo Springfield ever did. Let's fix it."

Too late. It's out there. And C is out there.


The author fundamentally misunderstands poor weather.

Portland's weather is pretty nice! Definitely better than, say, Chicago, or St Louis, or Minneapolis, or Buffalo. I'd argue also better than Austin or Dallas, though people are free to disagree about the merits of unbearably hot weather vs rainy cold weather.


Absolutely! I’d much prefer a mild temp with constant rain vs. wildly changing temps at extremes and crazy wind or storms.


Clearly a matter of taste then. I much prefer the extremes, and find mild weather boring. Maybe it's having grown up in England. Much happier where I live now and I'm at frequent risk of fires and floods!


Amen. Mild weather + usually gray > extreme weather + usually sunny. Though I'd never fault someone for making different decisions.

The fire season of the past few years are changing the equation for me though...


What's with all the Go hate on HN all of a sudden?


It is not all the sudden as far as I can tell. There's a long history of Go hate, and it makes sense to me. Go is a new(ish) and fairly popular language, which already guarantees some level of hate. More substantially, it is an implicit argument against most things language enthusiasts like about programming languages; it's lack of static features and dynamic capability, it's non-focus on optimality in any domain, there's no attempt at uniform elegance or a motivating theory, it's just a kinda mundane procedural language that tries to solve some problems C enthusiasts had while avoiding the things that bugged them about Java and C++ (to oversimplify). A language such as that succeeding socially and practically is borderline offensive to folks who love clever language and runtime design, who love things that can push the boundaries of performance or verifiability.

Something so apparently mundane and poorly thought getting traction is a regression in the world of software engineering, supported by a Big Evil Corp that many folks dislike.

I've also personally seen a social meta-effect of this, where in a particular space all of the language aficionados would make a point of dumping on Go whenever Go was discussed (or even when a dig at Go could be shoe-horned into another discussion), and at a certain point there are only negative discussions of it, and the snobbery (justified or not) is a form of social bonding.

Of course, there are loads of legitimate criticism to be applied to the language design, the runtime, the rollout, the marketing, the framings of the authors, but there's a persistence, a snarl, to some of the critics that seems to me to go beyond an observation of the real issues. For reasons listed above, some people seem to take hating Go quite personally.


The HN audience started out with fans of essays like http://www.paulgraham.com/avg.html and http://www.paulgraham.com/hundred.html, and now a language arose that seems to intentionally embrace the blub paradox.


What exactly is novel about Go? The only thing that comes to mind is go routines, but it is not integrated well in the language for several reasons, and concurrency is just a very hard problem. Otherwise it is the exact same thing as the litany of basic managed languages.


Why should a language be novel? If you have to hang something on the wall, are you going to discard screws and plugs because they've been around for quite some time?

Go is good enough. It has some short-comings. I'd like to see non-nullable types, even though I haven't had a memfault in ages. But it's easy to write it, runs fast, is memory efficient, and runs practically everywhere without a fuss. For normal software, that's such a big plus. I'd hate to have to go back to Java.


Well, the screws and plugs are Java and the litany of older managed languages we have with almost the exact same guarantees.

Why should I throw it away and buy a new set which will give me the same thing?


Languages have advantages and disadvantages, aside from being novel. Java is a memory hog in comparison to Go; Go is probably a bit faster in execution. The "eco system" for Go is pretty good, tooling too; I'm sure Java has a larger set of libraries and frameworks to choose from, but less easy to integrate. Idk about the current state, but Eclipse and Netbeans were so unpleasant when I did Java.

The choice is yours, novelty or not.


“Hogging” memory correlates with better throughput in case of GCd languages, and Java really shines on this front, it is not an accident that it is the numero uno choice for big backend services. Performance is also not really in favor of Go besides basic examples where value types can help. Any bigger example, and heap allocation won’t be avoidable and Java’s GCs are the state of the art to a huge degree.

And the final point, why reinvent the wheel each time? Go recreates a bunch of tooling, ecosystem that already existed.


A lot of people are treating it as an opportunity to port ___ to Go and put it on their résumés, which would be less compelling if you could easily reuse the original ___. We went through the same thing with “pure Java,” but those rewrites were at least improving memory safety and (often) error handling over C and C++.


Go has never been particularly popular on HN. It’s a bit on the nose, but lots of people here like to think of ourselves as clever (whether it’s true or not).

Go, philosophically, is a terrible language to show off in. It’s intentionally designed for every line to require as little brainpower to understand as possible. It’s not an aspirational language - unless you aspire to be able to hire lots of junior programmers and make sure they don’t cause too much trouble.

The lukewarm support go had here was because it was still new and trendy. Rust will lose its lustre in a decade or so too, and the tone will inevitably turn more negative. You can see the pattern slowly play itself out at the moment with docker.


Mēh!

> The lukewarm support go had here was because it was still new and trendy. Rust will lose its lustre in a decade or so too, and the tone will inevitably turn more negative....

Are Rust and Go not the same age?


> Are Rust and Go not the same age?

Rust only really started to get traction after 1.0 landed in 2015. Even then, writing web services and things like that in rust has only really been ergonomic in the last few years as async/await has stabilized. Rust is still a pretty niche language, and its much easier to admire something from a distance.

Go hit 1.0 in 2012, but people started using it in production before even then.


I'd say the 2018 edition was even more relevant (and that was late 2018, in fact). Rust was very hard to use prior to non-lexical lifetimes. So the real mass popularity of Rust is very recent.

Also why it's a bit silly to compare Rust to any other language (just pick your favorite: Go, C++, Java/C#, Python/Ruby, Haskell/Elixir, Javascript/TypeScript etc. and expect it to be just as popular. There's a whole lot of legacy projects written in older languages and they have to be maintained, even though some stuff does get ported to Rust in the meantime.)


People seem to get really upset about a language that does not do what they want, or how they want it to be done, so they write articles about the language nobody is forcing them to use.

I get that Rust or C++ has more power or expressiveness, but neither of those come without additional cognitive effort for us non-CS types. But if I don’t _need_ to work in Rust to get my work done, why bother ?

Go is good enough for me, Rust for others, etc. Hating a technology that you can choose not to use seems futile.


> language nobody is forcing them to use

Only if I were adamant enough to quit each time it happens (I say this having joined a team that was explicitly Java/Scala-focused, and yet.)


Rust fans trying to convince everyone it's the next big thing


wouldnt say its all sudden, this article has been linked multiple times before and there is probably 2-4 go hate threads per months on HN.


Previously:

https://news.ycombinator.com/item?id=31205072

(Edited to point to page 1.)


Why link to page 2?

If you want to begin at page one, try this: https://news.ycombinator.com/item?id=31205072

Edit: thanks for the follow-up @dochtman, it was just a little confusing. Cheers!


Googled the link, posted a little too quickly. Fixed it up.


The author doesn't use Go any more, but cares about Go so much. ;D


Definitely gets attention on HN for it.


That's a lot of words for a language you don't like and don't want to use.


> That's a lot of words for a language you don't like and don't want to use.

I agree, I'm glad the author wrote up such thoughtful constructive criticism. They clearly care about software development and want everyone to improve. :)


They didn't though.

It's ... familiar quibbles surrounded by noise. I'd love it if you could summarize the thoughtful constructive criticism you saw for the rest of us.


> It's ... familiar quibbles surrounded by noise.

I found it helpful. It did confirm my priors (Go is useful for a big class of problems) and educate me a bit (uninitialised values set to zero??)

I have compiled some Go (so fast) but written zero Go. I do not like garbage collected languages, but rather a garbage collector than reference counting (I have done a lot of Swift - Why? For money - there could be no other reason)

> I'd love it if you could summarize the thoughtful constructive criticism you saw for the rest of us.

The lack of protections for using uninitialised data structures. I learnt about that from this article. Makes me nostalgic for C


Go is getting weirdly high amount of hate I think I only saw something like that aimed toward PHP.

People seem to be outright offended that this language isn't a second coming of christ and dares to get some things wrong.


I seriously cannot tell if you are trying to be ironic here.


My point is that they're putting a lot of unnecessary effort and attention into being negative about something that they don't have to use anyway.

The author is making a living out of being a full time Rust online personality - https://fasterthanli.me/articles/becoming-fasterthanlime-ful... - maybe these unnecessarily controversial takes help them attract more attention and make a bit more money.


"Author is riling up the masses for clicks which generates revenue for him" is the conspiracy theory. It costs me money to serve the site and results in, at worst, DDoS attacks, and at best, hateful comments towards me.

"Author has built an audience around quality educational material that goes more in-depth than most others, and occasionally allows himself a rant" is closer to reality.

But, feel free to believe whatever you like better.


Can someone help me understand what are go's value types good for from a language design point of view? In most other gc languages, value types are always stack allocated and there' no way to take a reference to them, but in go you can happily do so, letting the variable escape its scope and thus be allocated on the heap and be garbage collected.

The only thing I can think of is a sort of substitute for immutability, so a function can take a variable by value rather than by pointer and guarantee that it won't modify it, but I don't see it being a win over just letting a variable be declared immutable.

The only language I can think of with sort of similar semantics is perl, but in pratice haven't seen many %hashes flying around, it's always $hashrefs instead.


I fail to see the point of this. Buried in this post are one or two reasonable (but familiar) criticisms of Go. As for the rest of it, the author appears to be upset that everyone in the world didn't immediately stop using Go after reading his blog post of two years ago.


The author does not think everyone in the world should immediately stop using Go. TFA even mentions that tailscale's use of Go could very well be the correct choice.

TFA is about dismissive comments about the article and people burying their head in the sand rather than engaging with the criticisms.


Or they are just fine with the drawbacks and are annoyed by another non-user whining about it.

Yet another whiner complaining about well-known problems that advanced users are aware of doesn't add anything and nothing in this article is new thing to any advanced user.

Especially if the asshole behind article makes the title extra confrontational and aggressive just to piss off people.


> Yet another whiner complaining about well-known problems that advanced users are aware of doesn't add anything and nothing in this article is new thing to any advanced user.

"Well-known problems that advanced users are aware of", in other words problems that you can and will shoot yourself in the foot with unless you've already shot yourself in the foot and know to avoid them? Why is it contentious to say that things should and could be better?

> Especially if the asshole behind article makes the title extra confrontational and aggressive just to piss off people.

Why do you feel the need to reproach constructive criticism as being from assholes and "whiners"? You can like Go, or anything else, while acknowledging its flaws and believing it should be better.

I thoroughly enjoy Kotlin, TypeScript, and a handful of other technologies but I will be the first to admit and lament their failings.

(I'll admit the title is cheeky/provocative, but I think calling the author an asshole and ascribing intent that they want to piss people off says more about you than them.)


>> Yet another whiner complaining about well-known problems that advanced users are aware of doesn't add anything and nothing in this article is new thing to any advanced user.

>"Well-known problems that advanced users are aware of", in other words problems that you can and will shoot yourself in the foot with unless you've already shot yourself in the foot and know to avoid them?

Not really. For example the mutex copying will be caught by tooling

    ./main.go:14:7: assignment copies lock value to b: cake.asd
and majority of problems can be summed up "it makes writing some code more annoying and verbose", not "introduces subtle and hard to find errors"

If you read the actual article you'd know that's what author complained most about, annoyances (and I pretty much agree with most of it).

The biggest "trap" being probably not being able to have proper immutable types but honestly so far that haven't bitten me in any way; the argument presented in article shows it off in really stupid way too, I'd think anyone assumes methods on a type (especially ones named Change()) would modify it by default and if you dont want caller to mutate it you wouldn't pass reference in the first place.

Better example would be passing a struct that have nested references as that would be a bit questionable as passing struct shallow copies it, but not the deeper levels that could be changed by the caller, so when bad enough coding practices were applied it might lead to something funny

Some other sorta-footgun is lack of timeouts on http/tcp calls (technically stdlib footgun) which means carelessly written code might get into bad behaviour, and the fact that map does not shrink (but that's implementation footgun) so long lived map will have max size it seen on its lifetime.


The author seems to be asking: given how bad Go is, why (sociologically) do people keep using it?

Of course, this only makes sense if you already accept the premise that Go is bad.


I think it may be an uncomfortable truth for many language enthusiasts that with a decent compiler/runtime and a reasonably skilled team, you can write very good software reasonably efficiently in even a mediocre language. Language design absolutely matters in terms of what can be expressed efficiently, what classes of mistakes are hard to make, what performance you can expect in normal code, but rough memory safety, some static typing, and basic abstraction gives you a lot of the value there, and you can close a lot of the remaining gap in language capability with some discipline and process. Naturally, ideally we'd still only use perfect languages, and not allow errors that could be avoided or suboptimal code, but not all PL power is free and personal taste/experience isn't irrelevant, so I'm not too surprised that some people prefer a tool that to me seems worse, because empirically it often works out quite well.


On and off with Golang and now stay with c++17/c++20(a subset of it that is to make it good enough and simpler for daily coding). I need c++ no matter what, adding Golang did not gain much so far plus it is a very different programming paradigm.


Rust seems closer to C++ than Go


Little bit of a nit on the title if the author is reading this. Go is called Go, not Golang.

https://go.dev/doc/faq#go_or_golang


To me, "Golang" is a good compromise to make material about it searchable. For example, Lean is notoriously hard to find stuff about. Try entering "lean" into HN's search engine:

https://hn.algolia.com/?q=lean

I wish there was a convention to call it "Leanlang" or "leanprover" or something.


That's 100% on Google. If they (a freaking search company) didn't name it "go" (an extremely common english word which makes searching harder) then it could just be searched instead of using things like 'golang' (was/might still be common name for the language in repositories) that are clearly about the programming language. I have the same conceptual gripe with other ones that use fairly common single words like "rust", "swift", and "python", but I don't (yet) use the first two to know how big of an issue it is, and I can usually use "py" instead to exclude any chance of snakes.


that "tradition" goes all the way to C sadly. But hey at least cmdline is short...


It is extremely common to use "Golang" for Go to disambiguation the extremely common word. Not necessary on HN due to context, but it helps with SEO for blog posts.


I always list it as Go/Golang on resume. Don't want people thinking I'm good at that board game


When people write phrases like "Proficient in C, C++, Go and Malbolge" on their resume it's always a pain to figure out if they mean the programming language or the board game.


I once interviewed someone who was "proficient in dart, go and nim", but I was puzzled when they said they never wrote a computer program in their life.


We once had an interviewer poke a candidate about PostScript basics, and it quickly emerged that he had put his uses of “print to file” on his résumé and had no idea it was a Forth-like language.


TIL that "nim" is a game as well as a programming language.


From what I've heard about its difficulty, I'd be far more impressed if you were proficient at Go.


Sounds like the exact attitude of Go developers, and the fact that they didn't think about searchability of their name says a lot about them (not like they work at a web search company and could have gotten that advice for free)


... as opposed to Rust, Ruby, Python, C, D, Java, Elixir, Crystal ? Only popular ones that doesnt collide with words are PHP and JavaScript (because it is amalgamation of 2 words). But you won't complain about those now, will you ?

About only semi-known ones that's not terrible to search is Haskell, and I guess Perl. And "letter with funny symbols after after" like C++/C#/F#


How many of those were created before search engines?

It's not about colliding with words, it's that "Go" is a common word even other languages context

But I'll give you a worse example than Go: R


How about we call it “The Google Language”?


Then it should be at least in the top 3 languages used by Google, shouldn’t it?


HN comment mentioned in TFA appears to be https://news.ycombinator.com/item?id=31194152


I actually like how this article is written, and I'm a big go fan.

I think the article has a conclusion it's trying to push to __stop using Go__. Maybe I misread it, but that's what it sounds like. Honestly though, I am fine with Go. Every language has its ups and downs.

What I like about Go is specifically what the article pointed out, the simplicity means I need to only remember a few concepts. In contrast, with Rust, or C, or C++, or Java, or C# I have to know _so many small details_ to not shoot myself in the foot.


> In contrast, with Rust, or C, or C++, or Java, or C# I have to know _so many small details_ to not shoot myself in the foot.

Java arguably has even less foot guns then Go and is an simpler language.


You can’t have a perfect language. Go is good enough for what is good at. Plus advanced IDE will help make the experience even better


Sure, no language is perfect but some things in Go were apparently "to make it simpler", but by lack of them it made the code in the end more complex and/or verbose.

Lack of generics made a lot of code less type-safe than it should, and just more complexity and/or duplication.

Lack of sum types made some types ugly (like mentioned IP type), and other ways of control flow harder. Rust-like Result<T> would be massive improvement to verboseness of error handling in Go for example, without really any drawbacks.


That doesn't mean there nothing to be learned from mistakes previous languages made and striving to create iteratively better languages


Go is OK. I think early adoption was mostly among Chinese startups due to the language being tied to Google and Pike.


I've never written a line of go in my life, but I totally disregard these kind of definitive and negative opinions about stuff (here a computer language) that are widely used. If so many people use it that means it's not completely garbage sorry


C# has channels as part of the standard library and is much preferable to Go imo


I'm a Go fan and the author makes some good points. That said, their arguments seem a bit one sided and there are a number of areas where they get close to, but miss what I feel should be the real conclusion.

I'm going to focus on toolchain related issue because I feel better able to speak on these topics. The language issues the author brings up are similar though.

RPCs instead of FFI. There is some truth here. I personally find wrapping foreign code in a C interface and using CGO to be pretty easy, but RPCs are a good choice as well. The author then jumps to this necessarily including TCP overhead which is wrong. If a FFI is the alternative, then running on a single machine is acceptable and a Unix socket is an option (I'm sure there is something similar for Windows).

I have often combined these strategies where I wrap some C++ code in a C library and integrate it into a CGO gRPC service which serves on a Unix socket. I do this because I find setting up an RPC service in Go to be much easier than C++ and I want to isolate the C/CGO.

Using an RPC interface also limits the unsafe code to a process which can be optionally further sandboxed (e.g. with SECCOMP). If you are concerned about memory safety, this might be a good idea even if you are using two languages with better FFIs. Even if both languages are memory safe, the FFI almost never is. In addition to the security benefits, this also makes debugging memory issues easier as it limits their scope.

Another benefit to FFIs being bad is that it has pushed Go to be one of the only languages where most code has no C dependencies. Nearly the entire standard library is pure Go and the parts that aren't are not commonly used or provide optional functionality which is not commonly needed. Most third party libraries I find on GitHub are pure Go as well and only have pure Go dependencies. The majority of Go binaries I have encountered either are or can be built with CGO disabled.

This has two major benefits.

First, it makes Go code much safer. C dependencies are a major source of memory unsafety bugs. The most common C dependency (in Linux land anyway) is glibc. glibc is well known for its terrible code quality and history of security vulnerabilities. Go is one of the only languages which makes it easy to not depend on it. Since the author brings up Rust, I would personally feel better about untrusted data being processed by a pure Go program than a Rust program with C dependencies.

Second, even the best FFIs make code harder to read. If nothing else, now you need to be familiar with both languages. Go libraries usually being pure Go makes it easy to jump into random library code and figure out what is going on.

The author also mentions build systems with a vague comment about cases not considered important by the Go authors. In my opinion, the true build system for Go is Bazel. Bazel has great Go support because Go was built with Bazel in mind. The fact that Go and Bazel handle imports in almost the same way and that Bazel BUILD files can be generated from Go code is not an accident. Bazel is very powerful and is usually a good fit when you start running into the limitations of the built-in Go build system.


If this guy is good at anything, it's generating comments and driving traffic to his blog.


Programming languages are subjective mediums, with no real objective measures, and that the negatives of the language are part of what gives it its character is is why people like it, not why they dislike it.

Reminds me of a diatribe by an oil painter on why people shouldn't use acrylic paints.

Probably mostly a cathartic thing after the author took a job that needed it before trying it out.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: