But I don't find myself agreeing with his position, which is "you shouldn't use Go for production services" (he explicitly says this in one of his Go posts, I forget which one and don't have time to look right now).
The better alternative to Go is Rust. Okay, sure, I'm willing to admit that in the examples presented, Rust handles things better. But Rust isn't a panacea. Rust is complicated and hard, and often it's not worth taking on that burden just to theoretically handle cases that rarely occur and even more rarely cause any problem. Programming is a means to an end, and the cost of using Rust (hiring, increased development time) is often not worth it.
The reason Go is successful is because it's easy for companies to use to solve production problems with teams of varying expertise. Its stdlib is well-featured, its ecosystem is good. There's generally one correct way of doing things. The same doesn't appear true in Rust. For example, an _incredibly_ common thing to do in production code is to make a web request. In Go, there is no need for debate, you use the stdlib. In Rust, you have to use a crate, which requires a decision to be made. Even worse is the async story, in Rust you have to decide whether to use Tokio or whatever. That burden is just not present with Go.
Btw this article should not be flagged and it's pathetic that people have flagged it.
I agree with this. I learnt Rust before Go, and using Go makes me feel like The Oatmeal piracy guy[1]:
"I'm not sure if I should use Go to write this HTTP service. I'd lose immutability tracking, I'd lose compiler-enforced thread safety, I'd lose the powerful type system, I'd lose the comprehensive error handling, I'd suffer from a million little papercuts, I'd have to use the weird date formatting system, I'd have to check nil pointers, I'd...
...oh, it's seven days later and I've already accomplished more writing networking servers and clients in Go than I ever have in years with Rust."
This isn't to say the points raised about Go aren't true. They are true, and if a better language were available, I wouldn't stand my ground and argue their benefits, I'd switch to it. The last comment I happened to post on this website is about how Go is insufficient without its army of linting tools [2]! Yes, I'm incredibly happy to have learnt both Go and Rust as their combination has expanded my skillset and the range of programs I'm willing to write tremendously. But if someone said to me "you should just use Rust instead of Go for your production services", I'd think the "just" was doing some incredibly heavy lifting.
An article that I'd like to see is one comparing the two languages for this niche (networking servers and clients), contrasting not just the language pitfalls but the third-party libraries necessary, the iteration speed, and the choices you'll have to make up-front. My guess is that the languages would be judged more closely together.
[1]: https://theoatmeal.com/comics/game_of_thrones [2]: https://news.ycombinator.com/item?id=30749921
> often it's not worth taking on that burden just to theoretically handle cases that rarely occur and even more rarely cause any problem. Programming is a means to an end, and the cost of [Edit] adding if err != nil to every line of code [/Edit] (increased development time) is often not worth it.
I basically like Go, I really want to like Go, but as a Python person who has used exceptions with good success for 13 years in HashBackup, I can't see myself adding an if test to practically every line of Python code, just to catch an exception that might occur once in a blue moon.
A lot of software is not so mission critical that lives depend on it. If that is the case, sure - add error checking to every line of code. But for a lot of software, IMO, it's too much work for too little reward. If something blows up in production in an unexpected way, sometimes that's okay. You log the problem, fix it, and it's fine.
I re-read the Go error handling page before posting, because maybe it's not as bad as I had initially thought. Nope, still looks pretty cryptic to me:
* First you construct a request, and check the error on that
* Then you "Do" the request and check the error on that
* Then you check the status code (you did check it, right?)
* Then you deserialize the response and check the error on that
* Then you close the response body (you did remember to close it, right?) and check the error on that
Actually making an HTTP request with Go stdlib is a many-stage process and it is very easy to screw up or forget one of the steps. I see it all the time. Actually I have seen outages related to people being too zealous and reporting errors related to closing the response body on otherwise successful requests.
Go and Rust both have strengths and weaknesses.
And I think they both have a love/hate dynamic because they make pretty strong choices in their own ways, and so people react strongly in response. Personally I'm a fan of both.
And yet, Go's HTTP client doesn't have a good default [1] because of zero values, which are argued harmful in the article.
[1]: https://medium.com/@nate510/don-t-use-go-s-default-http-clie...
Nope, a better alternative is Java/C#/Kotlin/etc. Those offer sum types, async programming, while being more productive than golang, and provide superior tooling and a superior developer experience.
Now suddenly we go from a poor type system (Java) to a decent one in Go (this is my personal opinion). Or from no type system to SOME type system. On top of this, we are getting all the benefits of a compiled language.
Essentially this is looking Go as a system language, while I think of it as an application language.
Aside from that, the author is right in some ways.
No, the better alternative is thousands of different languages.
HN is just hype-driven as usual and can see only Go and Rust.
You may say it's the snark/anger/frustration that got flagged, but I suspect it would not have been flagged if the topic were different.
Presuming the author is making bad-faith arguments for internet blog points goes against the spirit of HN. I prefer to draw my own conclusions, thank you. I normally expect HN to take the higher ground with calm and reasoned counter arguments of the content but not today I guess.
The way `nil` can seep into your codebase in Go and how bad the FFI is in Go will prevent me from ever choosing it for a project going forward. And I personally would not feel confident modifying Go code written by someone else, and would not feel confident accepting PRs into my Go codebase unless I understood every line inside and out.
Rust is a breath of fresh air in that regard.
All that said, I think the author here is idealistic in a certain sense. In the scale of "solving a problem by hand (or using Excel/a proprietary tool etc)" to "writing a computer program to automate it", both Go and Rust are very close to each other. And once someone learns Go and knows it well enough, the incremental cost of learning Rust or even thinking about the problems Rust solves for you becomes not worth it. I think that it's sad that this is the way it works, but that's just how it is. It's easy for settle for a local optima like Go when taking into consideration family, pets, salary, time, effort etc.
I do think on a long enough timescale, programmers will eventually move to a "better" language. But in my opinion, that time scale exceeds the lifespan of a human :)
My whole career people have been telling me to stop using languages or tools I've been productive with. I've built successful companies where everything was written in python, the whole time people on HN telling me how I was using a lowly language because of things like whitespace significance or strings that were not unicode by default.
If you care about other stuff, fine. If you don't think go works for you, don't use go. Just stop telling people who are using go (and other productive languages) to stop using them because of obscure language design issues or obscure API choices that people rarely encounter day to day. That isn't what matters to most people, and it's actually bad advice.
The problem is that there are a ton of less experienced people reading stuff on HN (just as I once was) who will take this to heart but have nothing actionable to gain from it, except to think they're a bad developer using a bad language. They aren't, and they're not.
I do think he's misunderstanding that the design intent is "networked C" and that Go + Protobuf is much better than he thinks. A lot of his complaints with the language and runtime boil down to:
- It's got a primitive type system (yep, nobody disagrees)
- It leaves a lot of problems to you (yep, just like C)
- you can't integrate it into existing binaries (yep, distributed systems connected by RPC)
He comes to terms with the last one and admits it, but it is not only the thing Go is good at but it was the design intent. If you start from "we want a language and runtime for writing distributed systems that would otherwise be written in C or Java" then Go is pretty good!
I'm a happy Go, Rust, C, and C++ person. They're each for different things. That Rust worked out (is working out?) despite being an incredibly powerful and complex language (compared to C) is awesome. In the mid-2000s, it's not clear that you'd bet on a language like Rust. It is clear you could do better than Java or C++ for distributed systems programming, and Go hit that pretty reasonably (I do wish the language had a better type system, and I'm glad to see Ian finally land generics, but I'd absolutely reach for Rust when writing code in a small group that can handle the "language complexity" in exchange for the benefits).
I've been coding professionally since 1987, using everything from mainframe assembler, Fortran, C, VB, Java, C++, to most recently Go. IMHO, the language itself plays a smaller role in its usefulness than most think. As important is the tooling, stdlib, ecosystem, community and "StackOverflow"-ability.
Go has a few warts, like every language, but not that many, and they are more than made up for by how easy it is to assemble and run a project using it. It's that that makes it the most productive language I've used so far.
Go made prototyping our product easy. We have relatively rarely needed Go expertise on the team to make the product better, when we did it was for the exotic iOS environment (not normal iOS, the Network Extension).
It’s far from perfect (and I really should write an experience report about all the times Go has let us down!), but it gives us less trouble than Node and Swift do, for our relatively minor uses of those languages. I don’t think any other language would have been a better choice, even putting our expertise aside.
On the whole I would say you don’t need Go experts on your team to build products in the language.
I will take all the footguns in the world for that ability. Plus in general the tooling is just amazing. I know there is other Unix tooling out there but I'm sorry, they kinda suck and are terrible to use. They're for the type of person who can use vim with their eyes closed and unfortunately that's not me.
The other footguns he mentioned? Valid. A linter will catch a chunk of them though. I just generally don't think they matter half the time. I think of it like the iPhone. It's a pleasure to use like 80% of the time maybe 85% and 10% it feels hacky and terrible to get something working and 5% you just can't or shouldn't do it.
So for the 80% use case (backend crud apps or backend web apis) I'd take Go every time. Plus it compiles almost instantly versus multi minute turnaround times in a lot of other languages.
However, with respect to the points about Go as a prototyping/starter language, there is not better language to start writing a project with in my opinion. Lots of languages have big communities of packages of various levels of maintenance but almost no other language has a standard library that is as usable as Go with the same guarantees between versions. I think its the biggest downfall of all these new languages like Rust/Zig/Hare etc, they all go for these minuscule standard libraries. I have no wish to start a project with any of them as all of them would involve I hunt down the 'best' http library and the 'best' async library and weigh their upsides and downsides, so I just reach for go and crack out the code I need to get it done, nothing else lets me do that half as easily, maintenance may be harder but at least I'm probably going to spend less time maintaining the list of packages that are still usable.
Go is good for onboarding new devs because it's simple. Our existing PHP devs were taught Go and it was painless. Jumping from PHP straight into Rust would be pretty painful, I think. Finding Rust devs isn't easy in our town.
But I agree with most of the points. You got to be very careful when writing in Go, because there are many gotchas. So many gotchas that I had to write a large document describing all the conventions and rules to remember to write bug-free (hopefully) Go. This "Go with conventions" reminds of the days when you'd try to simulate OOP in C - it works but error-prone.
Many of the problems are caught early with good unit and integration testing, however, just like with scripting languages. In my experience, most of the bugs in production in our Go microservices come from logical mistakes, not from misusing Go.
To those who do use Go: someone can call your baby ugly and you can still love it.
I think Amos has presented more proof than I can on the topic of just how Linux-influenced Go is. And I think it is fine for the majority of Go users because the majority users of Go are building server apps or Linux CLIs.
Amos has spent some time building cross-platform desktop systems with Go for itch.io and I think I'm seeing some of the same things they are in that scenario.
I think this is a reasonable article. I didn't notice anything too flame-y myself but if for you Amos gets flame-y at any point I think that's worth ignoring because there does seem to be something up with Go in cross-platform applications. Amos does have good experience here. (Go look at itchi.io's github like wharf [2] or butler [3] where they are/were the main contributor.)
I like Go a lot and for most things I'd keep using it still. Just sharing some observations.
[0] https://github.com/multiprocessio/datastation
[1] https://github.com/multiprocessio/dsq
So while this article didn't really convince me of anything, I have to admit I did learn a thing or two (or five). Kudos to the author, who is a fantastic writer and communicator. Even if you aren't neck deep in the language wars, it's worth reading.
Working in Python, I constantly run into errors that static typing could have caught. Working in Rust, I feel like I'm constantly fighting the borrow checker. Maybe a more experienced Rust programmer would feel differently, but one of the strengths of Go is that it's relatively easy to learn
I generally heavily preference scripting/interpreted languages over compiled languages, because compiled languages have a bevy of stuff that qualifies as "getting in my way". Go is refreshingly not like that, and therefore is the primary compiled language I use these days, and largely the primary language I use at all. Then again, I'm not an SWE. I'm a SysOps/SRE turned PM-T. So it's unlikely I would ever dig deeply enough to care about the particulars of a language's design other than knowing which problems it is best at and which problems a better choice can be made to solve.
Both this article and his previous article, I've read thoroughly. I understand and appreciate the criticism. But fundamentally there's seems to be an implicit assumption within both articles that other developers who use Go don't already know (most of) these things. Anyone who uses Go long enough will eventually run into some of its shortcomings. I'm not sure exactly why it rubs me the wrong way, but it feels like there's something subtlety insulting in the way the author presents his arguments. Like if we weren't all stupid rubes we'd just use whatever his preferred language is (Rust, presumably?).
The way people get zealoted about languages these days (especially some pretty terrible ones for getting things done) tempts me to just go back to writing Perl. In all the languages I've used over the years, I've probably got more raw work done in Perl than anything else, and it's still near and dear to my heart, despite being objectively a badly designed language.
I appreciate the nil method receivers, the zero value meanings, the types that are actually hidden pointers, but if you're not coming from C, a lot of it sounds inconsistent and counter-intuitive. And I'm not sure there's a way to justify it to programmers who have grown up on better, safer, clearer abstractions, even with the potential performance gains.
Without analyzing the rest of the post (which I read without having the skill to fully comprehend), this stuck out to me.
Perhaps the language behaves one way and not another by design? It's abstraction. By using a Struct, I'm telling the function "don't worry what is in this one box of data, just take it and process it as best you can" while defining two inputs to a function says "I demand that two pieces of data be present".
I don't think it's an inconsistency to see this behavior.
> Go not letting you do operator overloading, harking back to the Java days where a == b isn't the same as a.equals(b)
Does this guy really not understand that in a mutable language, (eq ...) is not the same as (equalp ...) and should never be confused with it?
Also: operator overloading is the spawn of Satan.
There's something that I think is important to mention: there's a difference between theorical ways to make bugs and bugs that happen in reality. The author spends more time talking about theorical ways to make bugs than bugs that happen in reality. I'm all for the "we should be using good tools", but there's also a point where you shouldn't use a tool just to use it.
An example of that: at work we have a very huge codebase that's a pile of C++, C#, JS and TS. The codebase is more than 20 years old. Typescript is a relatively recent addition. I've seen part of the adoption, and talked a lot with the people that adopted it. And the conclusion is that it didn't really reduced the errors we have in production. We already have processes to cover for that: being careful during development, pair programming, code reviews, automated tests, manual tests. However, it made the development way easier, since Typescript has a better IDE support. But it didn't lead to use reducing our processes.
A lot of the article seems to be rationalizing bad experiences with Go due to bug happening in production. I don't want to reject the criticism entirely, but I think the language isn't the only thing you can blame here. And my own experience is that processes matters more than the language when try to write good code. It seems like the author has a different experience compared to mine, and I don't want to fall into the fallacy of "it's possible to write bad code in every language". But I think the author should recognize that people can have widely different experiences, and that there isn't always someone that's right and someone that's wrong.
I've read lots of thing about Typescript adoption and lots of people/companies mention a reduction of bugs in production. This didn't happen for us. That doesn't mean they're wrong, and that also doesn't mean that Typescript is useless for reducing bugs in production. It means we have different experiences. I think it would be more productive to talk about our experiences, and try to find out why they are so different. This way we could learn a bit more about software engineering, and develop more empathy for each others.
Edit: I'm really shocked by the downvotes. Can someone explain why my comment is worthy of being downvoted?
But the OP, in either article, zeroes in on narrow language/STL design choices and never zooms out to consider the ecosystem and how Go works in an actual software engineering team. Pardon the vulgar analogy, but this is like zooming in on a dog's asshole and going "what the fuck is this? It looks gross and shit comes out" without considering that it is an integral part of a well-functioning whole.
It all comes down to misunderstanding each other's positions.
That is a super weak move.
The multidisciplinary lab I was in had a "Go or Rust" dilemma. As the language guy, I was asked for my opinion.
I had no experience with Go, and a small bad one with Rust: there was weird stuff going on with pointers, and even seemingly simple programs would fail to compile due to a less than happy borrow checker. Working with Rust required a heavy learning investment. On the other hand, Go looked simple, with amazing goroutine, fast compilation time (great for the code/compile loop), and backed by a big company, which is reassuring for then still young languages.
As I could not form my opinion on significant coding experience, I looked at the principles. It appeared to me that Rust design was guided by language principles, whereas Go was guided by system/engineering requirements. For example, Rust had generic and sum types, which Go lacked. Rust error are "naturally" dealt with using sum types, whereas Go relied on error codes. Go had that "operating system flavor", with features usually closed to the OS directly implemented in the language (goroutines, which are great). Rust had that "things are complicated flavor", in both the language (borrow checker, several kind of pointers), and the library (several kind of string?!) which at first is of-putting.
Being a language guy, I argued for Rust's good use of language principles over Go's more practical approach. My bet was that Rust usability would improve. A mix of Java/Scala was chosen. I now code in C++...
--- --- ---
A note on simplicity. Simplicity is a lie. Things (not only programming languages) are not simple, they are messy, hard. Truly hard. In my experience, simplicity claims are usually superficial, and show their limits when going a bit deeper. As programmers, we like to have simple, generic, abstracted constructs, because it makes our life easier. But the world is not generic; it is full of special cases, corner cases, situations that do not fall in a nice hierarchy, which is its beauty. It took me too long to "accept" this beautiful complexity, and to stop fighting it. If you are still looking for simplicity, maybe check that you are not fighting the world's complexity.
I'm not proficient nor use at $DAY_JOB C, Go or Rust. However, I wanted to write an Emacs dynamic module and those were my main choices AFAIK.
I've picked Go to write a grunt work module (fetching JSON, parsing it, transform it, send back to Emacs) and it's been smooth sailing.
I then found the benefits of writing client glue code between Minikube, direct K8s and Docker and Emacs a breeze.
What lie am I telling myself?
> Go's lack of support for immutable data — the only way to prevent something from being mutated is to only hand out copies of it, and to be very careful to not mutate it in the code that actually has access to the inner bits.
I thought everything was handed out as copies in go by default unless a pointer was being passed. So this would make it “easy” to tell whether you are mutating a object or not.
would appreciate if anyone can clear that up for me.
side notes:
since Im still a junior, not too interested in language wars and feel like everything i touch i learn something from no matter how old/new it is.
honestly, i would never have the confidence to critic this <language> in that fashion. i cant convince myself that i know enough about language design/systems to critic anything that harshly.
maybe its my shortcoming as a junior :)
I read articles like this, and think to myself ... some future CTO will curse my choices today. But then I feel that future CTO will curse any choices I make, and it won't be fair for her to curse my choices ... because I'm just trying to get us from default dead to default alive without running out of the small amount of money we raised.
Rust is absolutely not an option for us. We don't have the skills or time to write a Rust backend right now.
To me, as a scripter-ops kind of person, Go is useful as a Python variant. You get types, you get a huge standard library, and much of ops is network-based communication and glue code. The last job I had, I was tasked with rewriting a moderate Python lambda into golang as a POC for ease of use, and performance.
I relearned the basics of go in about two days, used google and docs copiously, but otherwise did not change the structure of the program.
The execution time was cut in half! It's an easy language to get using quickly and serves that purpose well. I wonder if the author could acknowledge the comparison to Python.
It's very unclear what this is supposed to mean. 'type Foo int' in Go creates a new 'Foo' type that can't be used as an int without casting (a̶l̶t̶h̶o̶u̶g̶h̶ ̶y̶o̶u̶ ̶c̶a̶n̶ ̶a̶s̶s̶i̶g̶n̶ ̶a̶n̶ ̶i̶n̶t̶ ̶t̶o̶ ̶i̶t̶). If you don't want to allow that, you can just wrap it in a single member struct.
I wouldn't use it for anything too complex, or requiring a GUI. Mainly because I'd be much better off using the languages in my day job that I'm more familiar with.