Hacker News Clone new | comments | show | ask | jobs | submit | github repologin
Lies we tell ourselves to keep using Golang (2022) (fasterthanli.me)
195 points by reillyse 2 hours ago | hide | past | web | 181 comments | favorite





This article makes a lot of great points about the shortcomings of Go. I don’t think explicit error handling is one of them however. I’ve previously spoken about my loathing of exception handling because it adds a “magic” layer to things which is way too easy to mess up. From a technical standpoint that isn’t necessarily a good argument, but from a pragmatic standpoint and decades of experience… well I will take explicit error handling which happens exactly where the errors occur every day. You can argue that Rust does it in a more elegant way, and I prefer it for personal projects. For big projects with a lot of developers of various skill level joining and leaving I think Go’s philosophy is one of the sanest approaches to error handling in the modern world.

Staying in that lane. In my part of the world Go is seeing adoption that no other “new” language has exactly because of its simplicity. It’s not the best language, but it’s often the best general purpose language because it has a lot of build in opinions which protect you from yourself.


I see a lot of people say this about exceptions, and I don't have that problem. The exception bubbles up until something catches it. You either catch it nearby and do something specific with it, or it bubbles up to a generic "I'm sorry there was a problem please try again later" handler.

Honestly makes me wonder what I'm missing. Maybe it's because I don't deal with state much? Do the problems start to mount up when you get into transaction locks, rollbacks etc? But I don't see why you wouldn't have the same problems with Go's mechanism.

Hoping to gain enlightenment here.


There are several shortcomings with go's error handling. The author heavily lies onto rust, so the alternative is not exceptions but a `Result<T, Error>` sum type.

No stacktraces and error wrapping forces you to not only invent unique error messages. You must also conceive a unique wrapping message at every call-site so that you can grep the error message and approximate a stacktrace.

The weird "return tuple" , which obviously just exists for errors because there is not a single other place where you can use tuples in the language, and the awkward variable initialization rules, make it so that you use the wrong `err` var at some point. E.g. if you want to reassign the result to an existing var, suddenly you have to declare `var err error`, and if `err` already exists then you have to reuse it.

There should be an enum type in go, or instead of the bizarre "return tuple" mechanics exclusive for errors, they should have added a better syntax sugar for errors like rust's `?` sugar. Instead we have something extremely tedious and quite error prone.

> it has a lot of build in opinions which protect you from yourself

It does have opinions, but too often they seem to be there to protect the language from being criticized. Sadly, this works, as marketing (lying) is an important factor towards making a PL popular in today's market.


> The weird "return tuple" , which obviously just exists for errors because there is not a single other place where you can use tuples in the language

MRV, go does not have tuples.

Go is not the only language with MRV (as a special case) and they’re not necessarily bad, iirc Common Lisp uses them as auxiliary data channels and has a whole host of functions to manipulate and refit them.

Go is singularly incompetent at MRVs though, in the sense that only syntax and builtin functions get access to variable arity (e.g. if you access a map entry you can either get one return which is a value, or two which are the value and whether the key is/was in the map). So MRVs mostly end up being worse tuples infecting everything (e.g. iterators needing Iter and Iter2 because you can’t just yield tuples to for loops).


> MRV, go does not have tuples.

> MRVs mostly end up being worse tuples

I think you noticed yourself that you’re getting too hung up on terminology. Multiple return values are a half-hearted, non-reified version of tuples.


Exceptions are sum types, they just have different syntactic sugar.

Not really. Exceptions usually imply unwinding the stack, and the ability to catch at any point throughout the callstack. Result types are just 'dead' data.

These are fully equivalent in outcome, though often not low-level implementation. You can use try...catch (called panic...recover in Go) to pack a normal and abnormal return case into the equivalent of a Result<> type. Or just pass an abnormal Result<> back to the caller to manually unwind a single "layer" of the call stack.

Having programmed for over 30 years, including nearly a decade of C#, I would say exceptions are one of the worst ideas in all of programming.

They are just horrific gotos that any library can invoke against your code. They are pretty much never, ever handled correctly. And nearly always, after an exception is “handled”, the application is actually in an unknown state and cannot be reasoned about.

Even junior engineers have a trivial time debugging most go errors, while even experienced principles struggle with figuring out the true cause of a Java exception.


Many people against Go's error handling do not advocate for exceptions, but for a combination of an Either/Result type (for recoverable errors) and fully aborting (for unrecoverable errors).

Abort on the other hand is used WAY to liberally in Rust.

How I hate it, that every second function call can break my program when it's clearly not a "halt the world, it's totally unrecoverable that the user sent us nonsense" type.

Return a Result and get on with your life!


If a Rust function can panic, there's generally a non-panicking alternative. For example, `Vec` indexing has `vec[n]` as the panicking version and `vec.get(n)` as the version that can return `None` when there's nothing at that index.

Exception and explicit on-the-spot handling are not the only two ways to handle failing processes. Optional/result types wrapping the are a clean way to let devs handle errors, for instance, and chaining operations on them without handling errors at every step is pretty ergonomic.

Rust's error handling evolution is hilarious. In the beginning, the language designers threw out exceptions --- mostly, I think, because Go was fashionable at the time. Then, slowly, Rust evolved various forms of syntactic sugar that transformed its explicit error returns into something reminiscent of exceptions.

Once every return is a Result, every call a ?, and every error a yeet, what's the difference between your program and one with exceptions except the Result program being syntactically noisy and full of footguns?

Better for a language to be exceptional from the start. Most code can fail, so fallibility should be the default. The proper response to failure is usually propagating it up the stack, so that should be the default too.

What do you get? Exceptions.


I'm not sure why you bring up Rust here, plenty of libs/languages use the Result pattern.

Your explanation of what bothers you with results seems to be focused on one specific way of handling the result, and not very clear on what the issue is exactly.

> what's the difference between your program and one with exceptions

Sometimes, in a language where performance matters, you want an error to be handled as an exception, there's nothing wrong with having that option.

In other languages (e.g. Elm), using the same Result pattern would not give you that option, and force you to resolve the failure without ending the program, because the language's design goals are different (i.e. avoiding in-browser app crash is more important than performance).

> syntactically noisy

Yeah setting up semantics to make users aware of the potential failure and giving them options to solve them requires some syntax.

In the context of a discussion about golang, which also requires a specific pattern of code to explicitly handle failures, I'm not sure what's your point here.

> full of footguns

I fail to see where there's a footgun here? Result forces you to acknowledge errors, which Go doesn't. That's the opposite of a footgun.


> what's the difference between your program and one with exceptions

Because errors as values are explicit. You're not forced to use ? everywhere; you can still process errors however you like, or return them directly to the calling function so they deal with it. They're not separate control flow like exceptions, and they're not a mess like Go's.


No, because you end up with a function coloring problem that way. A function that returns something other than Result has to either call only infallible code or panic on error, and since something can go wrong in most code, the whole codebase converges as time goes to infinity on having Result everywhere.

Yeah, yeah, you can say it's explicit and you can handle it how you want and so on, but the overall effect is just a noisy spelling of exceptions with more runtime overhead and fewer features.


> Once every return is a Result, every call a ?, and every error a yeet

The Try operator (`?`) is just a syntax sugar for return. You are free to ignore it. Just write the nested return. People like it for succinctness.

Yeet? I don't understand, do you mean the unstable operator? Rust doesn't have errors either.

> what's the difference between your program and one with exceptions except the Result program being syntactically noisy and full of footguns?

Exceptions keep the stacktrace, and have to be caught. They behave similar to panics. If panics were heavy and could be caught.

Rust errors aren't caught, they must be dealt with in whatever method invokes them. Try operator by being noisy, tells you - "Hey, you're potentially returning here". That's a feature. Having many return in method can both be a smell, or it could be fine. I can find what lines potentially return (by searching for `?`).

An exception can be mostly ignored, until it bubbles up god knows where. THAT IS A HUGE FOOTGUN. In Java/C# every line in your program becomes a quiet return. You can't find what line returns because EVERY LINE CAN.


> never handled correctly

I’ve seen this argument, but if you look at real golang code and examples, it’s just a bunch of “if err <> nill” copy pasta on every line. It’s true that handling errors is painstaking, but nothing about golang makes that problem easier. It ends up being a manual, poor-man’s stack-trace with no real advantage over an automatically generated one like in Python.


Which could be solved in one swipe by adding a Result<T, Error> sum type, and a ? operator to the language. This is more a self-inflicted limitation of Go, then a general indictment of explicit error handling.

But with Exceptions you can easily implement multiple return types in e.g. Java ;)

I shocked my Professor at university with that statement. After I started laughing, he asked me more questions... still went away with a straight A ;D


Hard disagree. Exceptions are actually good. They make code clear and errors hard to ignore. I've written a ton of code over decades in both exceptional and explicit-error languages and I'll take the former every day. There's no function color problem. No syntactic pollution of logic with repetitive error propagation tokens.

Also, exception systems usually come with built in stack trace support, "this error caused by this other error" support, debugger integration ("break the first time something goes wrong"), and tons of other useful features.

(Common Lisp conditions are even better, but you can't have everything.)

You can't just wave the word "goto" around as if it were self-evident that nonlocal flow control is bad. It isn't.

> And nearly always, after an exception is “handled”, the application is actually in an unknown state and cannot be reasoned about.

That's not been my experience at all. Writing exception safe code is a matter of using your language's TWR/disposable/RAII/etc. facility. A programmer who can't get this right is going to bungle explicit error handling too.

Oh, and sum types? Have you read any real world Rust code? Junior developers just add unwrap() until things compile. The result is not only syntactic clutter, but also a program that just panics, which is throwing an exception, the first time something goes wrong.

Many junior developers struggle with error handling in general. They'll ignore error codes. They'll unwrap sum types. They might... well, they'll propagate exceptions non-fat ally, because that's the syntactic default, and that's usually the right thing. We have to design languages with misuse in mind.


> Have you read any real world Rust code? Junior developers just add unwrap() until things compile.

If you really don't like unwrap[1], you can enable a linter warning that will let you know about its uses to flag it during code review. You know exactly where they are and when they happen. Exceptions are hidden control flow, so you rely on documentation to know when a function throws.

> Writing exception safe code is a matter of using your language's TWR/disposable/RAII/etc. facility. A programmer who can't get this right is going to bungle explicit error handling too.

Rust has RAII, so you don't have to worry about clean-up when returning errors. This is a Go problem, not Rust.

[1] https://blog.burntsushi.net/unwrap/


Bah, no, I hated that you had to wrap basically every code block in a try/catch in Java, because the underlying lib could change and suddenly throw a Runtime-Exception.

At the same time Checked Exceptions were a nightmare as well, because suddenly they were part of the contract, even though maybe wrong later.


> the underlying lib could change and suddenly throw a Runtime-Exception.

And what would you do in that case? Since this is a future change your existing code presumably wouldn't know what else to do but throw its own exception, so why not just let that one propagate?


Checked exceptions are more trouble than they're worth. That doesn't make exceptions in general bad.

As said, I don't like the wrapping of about everything with try/catch

Sure, you can only do it way up the stack, but that's not enough quite often.

If you can only do it all the way up, I find it ergonomic.

Maybe I should experiment more with catch unwind in Rust.


Without fail, every single person I’ve seen rave about go’s error handling compares it only to exceptions as if that’s the only alternative.

On the flip side I have yet to find a person who’s familiar with sum types (e.g., Maybe, Option, Result) that finds the golang approach even remotely acceptable.


Here, you've found me.

I don't LIKE it, but it's acceptable.

I have been working with Rust since 2015 (albeit not professionally, but a lot of side projects) and love it.

But I also dabbled into go the last couple of months and while it has its warts, I see it as another tool in the tool-belt with different trade-offs.

Error handling is weird, but it's working, so shrug


Sorry, balanced opinions are not welcome in discussions about favourite programming languages.

I dislike sum-type based error handling. It is annoying syntactically and only really doable with lots of high-level combinators, which in turn hinder debuggability.

FWIW... WebAssembly has Option and Result, and adapters for Go.

What do you mean by this? WebAssembly is a low level bytecode which only defines low level types. WebAssembly doesn't "have" types any more than x86 "has" types right? Or have I missed something?

That’s to be expected since it is marketed towards beginner and casual programmers.

You hit the main gripe I have with Go, its types system is so basic. I get people raving type-correctness of Go when they come from Python but the type system in Go is simply pre-historic by modern day standards.

I feel that the future for Python people who want type safety will eventually be TypeScript on nodejs. Go was intended as an alternative to C++. It seems that in reaction to the ungodly complexity of C++, the creators wanted to avoid adding language features as hard as possible. If the user could work around it with a little extra verbosity, it'd be ok. I feel they removed too much and maybe not the right things.

Go’s type system is not even impressive compared to python’s.

Do you have a pydantic equivalent in go? Also modern typing in python is starting to be OK to be honest (well, if you consider typescript typing OK), so it isn't really a knock on Go :)

Well I was comparing to python codebases before they added type annotations

this part of error handling is pure religion. it goes even against one of the most basic go tenents. that code should be easy to read not write. Try reading and understanding the logic of a particular method where 75% of the lines are error noise and only 25% are the ones you need to understand what the method does. yes it's noise because whenever read a codebase for the first time you are never interested on the the error edge case. first glance readability needs to tell you what you are trying to accomplish and only after what you are doing to make sure that is correct.

on this point go's error handling is a massive fail. Notice that I'm not saying explicit error handling is bad. I'm saying the insistence that error handling needs to be implemented inline interleaved with the happy path is the problem. You can have explicit error handling in dedicated error handling sections


I would certainly argue against the claim that explicit error handling is far overkill.

Where I agree: It forces you to think about all of the possibilities your code might generate. (This is more of a C question than it is with other languages)

However, when abstracting blocks of code away, you don't always need to handle the error immediently or you may want to handle it down the stack.

You're giving up a lot of readability in order for the language to be particular.


> It forces you to think about all of the possibilities your code might generate.

Except it doesn't actually. You can totally just ignore it and pretend errors don't exist. Lack of sum types/Result, and pointers as poor mans nil, really hinder's Go's error handling story.


Rust handles this much better.

Error handling is still explicit, but it gives you the tools needed to make it less tedious.


For me, the issue with error handling is that while errors are explicitly stated, they are often poorly handled. Rarely have I seen the handling of multiple reasons for why an error might occur, along with tailored approaches to handle each case. This is something very common in older languages like Python or Java

As a regular Go user, I agree with this take. Though the tools exist, error wrapping and checking (with errors.Is and so on) is actually pretty rare in my experience.

Positive example of good and appropriate usage here: https://github.com/coder/websocket/blob/master/internal/exam...


When I started trying to teach myself Rust, the error handling story fell apart on me very quick.

Like as soon as I wanted to try and get sensible reporting in their, suddenly we were relieving libraries, adding shims and fighting mismatched types and every article was saying the same thing: haha yeah it's kind of a problem.

I'm very, very unsold on explicit error handling compared to exceptions for practical programming. The number of things which can error in a program is far larger then those that can't.


The problems you're describing don't exist in go. There is exactly one standard type that is used by everyone, at least in public API's, you can always just return the error to the caller, if you don't want to handle it in place. The main difference with exceptions in my practice is the fact that it's a lot easier to mess up, since it requires manual typing. This is probably my main problem with everything being as explicit as possible: it requires people to not make mistakes while performing boring manual tasks. What could possibly go wrong?

I felt the same but after switching to anyhow and thiserror in pretty much every Rust project I work on I find it quite painless. It's not ideal to rely on crates for a core language feature but I never find myself fighting error types anymore. Have you tried those crates? Do you still hold that opinion?

You don’t need crates for it, anyhow is basically a better Box<dyn Error>, if you just want the error signal you can use that. The main thing missing from the stdlib fur this use case is I don’t think there’s anything to easily wrap / contextualise errors built in.

> I’ve previously spoken about my loathing of exception handling because it adds a “magic” layer to things which is way too easy to mess up.

I kind of see your point. In this very moment, it doesn't matter whether I agree. What I don't understand, though, is why (typically) people who abhor exceptions are among the fiercest defenders of garbage collection, which does add a “magic” and uncontrollable layer to object destruction.

Personally, having learned to love RAII with C++, I was shocked to discover that other languages discarded it initially and had to add it in later when they realized that their target developers are not as dummy as those choosing Golang.


How does RAII works in concurrent systems ? It seems to me you need to add compile-time object lifetime evaluation (as in rust) which so far incurs a high toll on language complexity.

The exact same way it works in Rust. C++'s RAII works the same as Rust's Drop trait. The object is released when it goes out of scope, and if it's shared (e.g. Arc), it's released when the last reference to it drops.

It's actually very rare that it should be the caller who has to handle the errors.

Go, however, forces you to spread your error handling over a thousand little pieces with zero overview or control of what's happening.

Rust eventually realised this and introduced try! and ? to simplify this


More importantly, Rust has the notion of a result type and it is designed to be both generic and composable.

A problem I often face in Go and TypeScript code is code that ignores errors, often unintentionally. For instance, many uses of JSON.parse in TypeScript do not check for the SyntaxError that may be thrown. In Go, it is common to see patterns like

  _ := foo.Bar()
  // assume Bar() returns error
This pattern exists to tell the reader "I don't care if this method returns an error". It allows one to avoid returning an error, but it also stops the caller from ever being handle to the error.

Also, the position of the error matters. While the convention in the stdlib is to return errors as the final value, this isn't necessarily followed by third party code.

Similarly, errors are just an interface and there is no requirement to actually handle returned errors. Even if one wants to handle errors, it's quite awkward having to use errors.As or errors.Is to look into a (possibly wrapped) chain of errors.

The benefit of Rust's Result<T, E> is that

- position doesn't matter

- there is strong, static type checking

- the language provides operators like ? to effortlessly pass errors up the call stack, and

- the language provides pattern matching, so it's easy to exhaustively handle errors in a Result

The last two points are extremely important. It's what prevents boilerplate like

  if err != nil {
    return nil, err
  }
and it's what allows one to write type-safe code rather than guess whether errors.As() or errors.Is() should be used to handle a returned error.

I am pretty sure if it were for the Typescript creators they would not allow exceptions in the language, but they had to work within the confines of Javascript. Heck they even refused to make exceptions part of the type-system.

It is unfortunate that many of Typescript developers still rely on throwing exceptions around (even in their own typescript code). Result types are totally doable in Typescript and you can always wrap native calls to return result types.


Why would you "check" for TypeError being thrown? Just let exceptions in general propagate until they reach one of the few places in the program that can log, display, crash, or otherwise handle an exception. No need to "check" anything at call sites.

90% of the criticism of exceptions I see comes from the bizarre and mistaken idea that every call needs to be wrapped in a try block and every possible error mentioned.


Unsure if this is the right place to ask, but this conversation inspires me this question:

Is there in practice a significant difference between try/catch and Go's "if err" ? Both seem to achieve the same purpose, though try/catch can cover a whole bunch of logic rather than a single function. Is that the only difference ?


Try/catch can bubble through multiple layers. You can decide/design where to handle the errors. If you don't `if err` in Golang, the error is skipped/forgotten, with no way to catch it higher up.

You can decide not to catch a thrown exception, it travels upwards automatically if you don't catch it.

I think that's the biggest difference.

With Go you need to specifically check the errors and intentionally decide what to do, do you handle it right there or do you bubble it upwards. If you do, what kind of context would the caller want from this piece of code, you can add that too.


Rust and Go are very different and I feel people want a middle ground that just doesn't exist currently.

A garbage collected relatively simple language that compiles into a statically linked binary but has a type system similar to rust, rest types etc.

Syntactically, Gleam and Kotlin come somewhat close but not really. I like Rust but I do believe it is too complicated for many people who are capable of creating something but are not CS grads nor working as programmers. If you're only gonna use the language every once in a while you won't remember what a vtable is, how and when things are dropped etc. I understand that "the perfect language" doesn't exist but I think both Go and Rust brought amazing things to the table. I can only hope someone takes inspiration from both and creates a widely usable, simple programming language.


> A garbage collected relatively simple language that compiles into a statically linked binary but has a type system similar to rust, rest types etc.

Swift.


Try F# with the new AoT compilation option and publish single file switch.

> A garbage collected relatively simple language that compiles into a statically linked binary and has a [good] type system

Yeah! Pattern matching too. What are currently available languages closest to this? AFAIK, Gleam relies on a virtual machine, but otherwise seems promising.


Stretching 'currently available' a little, there's Roc lang [1]. Though still in development, you can use it for small personal projects. Another caveat is that it's a functional language, which could potentially hinder its wide adoption

[1] https://www.roc-lang.org/


  Go: "I'm a simple language!"
  User uses Go for some time.
  User: "I hate you, you're a simple language!"
Perhaps it's because I'm 50+, I love a simple language.

I feel the "critique" is not very balanced, and I view judgements that are not balanced as weak, as everything in technology is about tradeoffs.

I of course come to a different conclusion: https://www.inkmi.com/blog/why-we-chose-go-over-rust-for-our...


Go is "simple" insofar as you're doing simple things. If you've tried writing a KV store or database (as I have) you'll quickly find yourself wanting slightly more modern language features.

"quickly find yourself wanting slightly more modern language features."

Use the tool that works for you.


Feel free to take a look at any more complicated GoLang code (k8s, gorm, etc) and you'll see that the tool/library you're depending on requires a veritable rats nest of bad practices to work around the Go's inherent limitations.

I wish the discourse that Go is a "simple" language would die.

Despite its veneer, once you start writing Go it quickly becomes apparent that it isn't simple. Hidden complexity and footguns are abundant (e.g., https://archive.ph/WcyF4).

It's nevertheless a useful language, and I use it quite a bit, but it's not "simple".


It's easy not simple, but the consequence is that any complexity that other languages handles for you, in go gets forced onto the developer.

There's more stuff to think about, because the language is doing less for you.


I have no idea why anyone would say it's not simple, it's super-simple. Learning how duck typing works with interfaces and how to use it is perhaps the only hurdle. In my experience, only certain BASIC dialects like VisualBasic are simpler.

Yes, like "Opening Brace Can't Be Placed on a Separate Line" (from your link).

Everyone can read Go code and understand what happens. There are some minor difficulties like func (*A) vs func (A).,


RiscV assembly is even easier to read by that metric.

My assembler days were 4 decades ago, but

"Everyone can read Go code and understand what happens."

There seems to be a difference between "easy to read" and "understand what happens" - or what happens on what level. The challenge is that there is a tradeoff between the two. Assembler is too low to understand what "really" happens, on the other hand Haskell for example with Monad stacks is again very easy to read + understand what happens "most of the time", but hard to understand all the abstracted away side effects.

In Haskell with

   add 3 5
everything can happen beside what you see.

In assembler

  ld a, 3
  add a, 5
nothing happens except these two instructions.

The tradeoff is how much you want to be explicit, with the downside of creating too much noise, and how much you want to abstract away, with the downside of magic happening somewhere.


This 100%, I was just about to type a long rant up about this. There are so many weird parts of the language that took me forever to grasp, and in many cases, I still don't have an intuitive grasp of things.

And plenty of other examples that aren't in that article:

- You have a struct with an embedded interface. Does the outer struct satisfy the embedded interface? And can I type assert the outer struct into whatever embedded struct is fulfilling the inner interface?

- When should I pass by value and when should I pass by reference? Like I generally know when to choose which, but do I really know without performing a benchmark? And what about arrays? Should I store pointers in them? But it also seems that people just don't care and just randomly roll the dice on when to return a pointer or a value?

- Shorthand variable declaration. How does it work when you shorthand declare two variables but one of them already exists?

Don't both answering the questions, that's not the problem. The problem is that it's just not intuitive enough such that I'm confident I know the correct answer.


I am not going to answer the questions, but this is a very strange complaint, to be honest.

For example, passing by value/passing by reference is something covered immediately in the Go FAQ document once and for all. Everything is passed by value in Go, that is it. There should be no confusion at all. If you spend 15 minutes reading Russ Cox's post on the internals of the most common data types, you will also understand what data structures have implicit pointers under the hood.


Well yes obviously I know everything is passed by value, just like in literally every other popular language. I'm talking about the difference between pointer parameters/receivers vs value parameters/receivers.

Your thinking is too complex for Go. You might be better of with Rust.

Same about benchmarking, if you want and need the fastest code, or the best memory management, use Rust.

If you need something faster than Python in general but not the fastest, use Go.


But that's the thing right? Like I come from Java. In Java, we have objects. They are pointers. That's it. You don't get to decide on whether you want a pointer or a value (I guess primitives are an exception lol). But it was so simple!

And same in JavaScript. Everything is a pointer except primitives. That's it. End of story.

And I have written Rust too, and while the situation is definitely more complicated there, the guidance is extremely simple and straightforward: If the struct implements Copy, then it is very cheap to copy and you should pass by value. Otherwise, you should pass by pointer/reference.

And meanwhile in Go, I just see pointers and values being used seemingly interchangeably, and seemingly at random.


> but do I really know without performing a benchmark?

Not really. But that’s one of Rob Pikes rules [1], I think the intention is to write whatever is simplest and optimize later. The programmer doesn’t need to remember 100 rules about how memory is allocated in different situations.

[1] https://users.ece.utexas.edu/~adnan/pike.html


I mean it's a great idea, and I fully agree that I do not want to worry about memory allocation. So then why is `make` a thing? And why is `new` a thing? And why can't I take an address to a primitive/literal? And yet I can still take an address to a struct initialization? And why can't I take an address to anything that's returned by a function?

[meta] Does anyone else color schema? I found the blue links hard to read with the dark background.

Or maybe the links are left to the default for the browser to decide.

Either way I would prefer an easier to read color.


Every time I read a critique of Go, I feel the same way: I'm still going to continue to use it anyways. The reason why I am going to continue to use it anyways is because while I understand that it has plenty of easily documented issues in theory (and that people regularly do actually run into in practice), I still find that it is one of the better programming languages in practice anyways. Some of the things that people commonly list as shortcomings I don't agree with (I like the explicit error handling everywhere) and for other things... I often agree, but it doesn't bother me much more than the shortcomings of other programming languages, of which there is certainly no shortage.

I guess I feel bad for people who are particularly sensitive to the areas that Go does not succeed in, because they are probably going to be complaining about it for the rest of their lives. The truth is though, I don't use a bunch of rationale about what is the best programming language to choose which one to work on for a project, I choose a language that I feel works well for me, that I feel I can consistently write good software in, that feels, well, nice to work in. Being a person that values correctness, I do sort of wish that language could be something more like Rust, but for now, it's just not, though it's not like I hate Rust, it's just not what I reach for in a pinch.

Enough has been written about how terrible Go is by this point. At least now I know what it's like to have been a fan of PHP a few years ago! (That's an exaggeration, but it's not that big of one in my opinion.)


I sort of like Go. The explicit error handling is a little obnoxious sometimes, but it's just a way to get things done. Otherwise I see its simplicity as a strength. It is very ergonomic, easy to pick up, and generally performs pretty well. I perhaps wouldn't pick it for every scenario, but there are plenty of scenarios where it would be a good tool.

Then again, I sort of like Java and Python too, two languages I am proficient enough at. All of those are good tools for what they intend to be.

I don't understand why people get so passionate about programming languages. They are tools. You may like soke more than others, but that doesn't invalidate the ones you don't like.


> I guess I feel bad for people who are particularly sensitive to the areas that Go does not succeed in, because they are probably going to be complaining about it for the rest of their lives.

Well, that’s a stellar endorsement of the article, because that’s literally the point they’re making.

You’ll use go.

…and then regret it.

…but by then it’ll be too late, and you’re stuck with it.

I think the author makes a compelling argument, which is very difficult to counter, that it is a bad choice and you will regret having it in production in many of the explicitly listed cases, and in many professional situations where companies that are not technically competent use unsuitable tech.

Companies should stick to boring tools.

…but, for personal projects? Sure, go for it.


There's no tool boring enough to prevent any chance of regret. At the end of the day, it's really, really difficult to anticipate where your pain points will actually wind up in the real world. In practice, I've had lots of really good production success with Go and not too much heartache about the choice. Since adopting it personally (in around 2014) almost every company I've gone to work since has used Go in some capacity and that capacity was usually growing because it was working for them.

Will you regret choosing Go? Maybe. Or, maybe not.


Go is as boring of a tool as it gets. Which is why I will happily use it

No, because it's an example of someone who chose Go and didn't regret it, and continues to choose Go, because the objections of the article are in practice just not very important or compelling.

In my personal experience your average go developer that likes go, likes it because he doesn't really know anything else.

As a result most things written in go are, in my own experience, of lower quality compared to things written in other languages.

So using things written in go is usually my last resort, because I expect they won't be high quality before even downloading them.


There's plenty of software I work on from time to time that's written in Go which I'm happy are written in Go. Some of those were even projects which I started and chose the language for.

Then there's software I've been involved in where I believe Go was the wrong choice. And software not written in Go where I believe Go would have been the wrong choice.

My point is, Go is just another programming language, with a unique set of strengths and weaknesses, and there are plenty of cases which i have experienced myself where there are genuinely no regrets around picking Go. Not because there aren't shortcomings in Go, but because there are plenty of cases where those shortcomings don't matter.

In my professional life, I have seen significantly more regret associated with choosing JavaScript on the back-end than with choosing Go. But then again, there are services in production written in JavaScript where that just never has been an issue and there are no regrets.


Uhh, maybe.

Where is the tradeoff analysis? Yeah, you might regret using Go when some zero value you forget to fill in somewhere pops up later and ruins your pipeline. But are you considering all the issues that didn't pop up because you chose Go?

Java's boilerplate code? Rust and C++'s lifetime analysis and memory management? C's lack of tooling? Python/Typescript's runtime errors? Functional languages' tiny employee pool? How much did trouble did you save by avoiding these?


Go is very boilerplate. It requires at least 3 lines of error checking every 1 line of actual code.

Also it doesn't have packed structs so it's completely incapable of doing low level networking or to handle binary files (you can of course do all the bitwise operations yourself… but is it sensible to use a language that is more error prone than C in 2024?).

Also due to its lack of A LOT of system calls, you will need to use modules written by someone on github, which will happily segfault if you look at them funny.

So now you have hidden memory management! Fun!

Of course if all you do with go is a glorified jq script… sure then it's kinda fine.


I worked on a project with gopacket. It was completely fine.

Thoughts on C#?

When last I tried it, maybe around 2014? I found it a kinder, cleaner Java with better tooling. Visual Studio (not Code) is still the best IDE I've ever used.

Unfortunately it's not popular in the circles I hang around in and the places I've worked. Now that .NET core is the one true runtime I'd welcome an opportunity to try it again; alas, I doubt I'll have such an opportunity (at least not through work).

I remember the upsides but I'm sure there are downsides I'm not aware of. I'd love to read a critique from someone with real world experience.


Not that you asked me but since Go is my goto language, my thought on C# is that it looks pretty cool. C# with hill-climbing thread pool and async seems rather compelling. I really see only two (major, obvious) downsides with C#:

- It has so much. language. design. This is both a strength and a weakness, of course, but C# really does take this to an extreme. I won't bother making a huge list of examples because I think you get the picture.

- Microsoft needs to stop doing things that harm confidence in .NET. Between the silliness of removing hot reloading from the CLI, the situation around debugging outside of Visual Studio products, and drama around the .NET organization... I feel cautious touching the .NET ecosystem. That's saying something considering the company that backs the Go programming language.

(Note: I have not used C# in production, so I can't speak to what it's like in that case. Seems like it's at least fairly "boring" in a good way for a lot of organizations though.)


> I choose a language that I feel works well for me

Which is the wisest choice for everyone. Golang is only a problem when a manager imposes it on you.


"Manager imposes it on you" just means you work in a team rather than alone. You can pick whatever you like for side projects, of course you're going to use whatever your team uses otherwise.

"The key point here is our programmers are Googlers, they’re not researchers. They’re typically, fairly young, fresh out of school, probably learned Java, maybe learned C or C++, probably learned Python. They’re not capable of understanding a brilliant language but we want to use them to build good software. So, the language that we give them has to be easy for them to understand and easy to adopt."

Excerpt of the talk "From Parallel to Concurrent" by Rob Pike from Lang.NEXT 2014

https://youtu.be/uwajp0g-bY4?si=EcOXVaGJ1ObFILb6

People tend to forget that Golang was created on purpose for poor programmers.

===

Add: @zwnow

> The industry is still built upon people who can build fast. Go allows that and so does Javascript.

Sorry, I forgot we are on Hacker News, where confusing web development with the whole realm of software development is par for the course.


> People tend to forget that Golang was created on purpose for poor programmers.

Nobody is forgetting that quote. Trust me, it has been repeated a lot[1].

That said, I think this framing of the issue really needs to die. Rob Pike is saying they're "not researchers", that they're "typically fairly young", not that they're poor programmers. Notice that in the list of languages they may have learned, "C or C++" is present. The idea is not that Go is designed for people who can't possibly write C++.

This framing also implies that the language being better for n00bs means that it's also necessarily worse for everyone else. There are some tradeoffs where this is a defensible position, but I think on the whole it's just not generally true. A good example is preferring composition over inheritance: I think the former is generally more understandable and a lot of people actually contort C++ to use it this way too. (For example, in some codebases, only pure abstract base classes are ever inherited; everything else is final.)

When I see this quote repeated as if it implies that Go is just generally designed for bad programmers, I feel like it reads like flamebait. The real answer is that it was designed to be so easy that any idiot can use it. Or in other words, Go is very grug-brained[2].

To each their own, but it's been over 10 years since that quote and Go has evolved a lot. Is it perhaps time to put it to rest and stop reading into it so much?

[1]: https://hn.algolia.com/?dateRange=all&page=0&prefix=false&qu... - though I'm sure it has been paraphrased and linked even more than this.

[2]: https://grugbrain.dev/


> People tend to forget that Golang was created on purpose for poor programmers.

Poor programmers by Google's standards. I would argue the vast majority of programmers, even those outside of Google, don't want to be language researches, have no desire to be a language wonk, but want to build software to solve their and their companies problems. I read that quote and think it means that Golang is the only language the vast majority of programmers should be using unless they are researchers, not as a some veiled put down.


I managed to make go segfault multiple times (a real actual segfault). It's not a general purpose language. If you want to do things that aren't json RPC is awful.

You can call us poor programmers if that boosts your ego. The industry is still built upon people who can build fast. Go allows that and so does Javascript. You might not like it but that's what is earning most people their bread nowadays.

> The industry is still built upon people who can build fast.

Correction: the commercial software is, not the industry. The industry and corporations are capitalising on quality open-source software, meticulously written off working hours with a straight head and passion, and a great attention to details. The fact that you can write glue fast enough to satisfy your SLT is predicated on the presence of those quality components you're gluing together for free.


I don’t think this implies it can’t be the right tool for the job, even when everyone on the team is a good programmer.

I don’t think it implies that you can’t write good programs in it.


If it was created on purpose for poor programmers, it seems to have been created to enable poor programmers to write the poor code they wanted to write, instead of making it impossible for poor programmers to write any code.

I guess that's the difference, if you want code, no matter the quality, you have one choice, if you want code that's correct, you have another.


> People tend to forget that Golang was created on purpose for poor programmers.

Ergo: if you actually like it, you must not be that great a developer.


Probably, yes. Though that's an empiric finding, and doesn't follow logically from the premises.

Plenty of software engineers don't know any better themselves

More generally, when it's not well suited for the problem to be solved. Eager coworkers anticipating Google level traffic may want to write the system in Go and multiple microservices when a simple FastAPI server would do.

> Go does not succeed in, because they are probably going to be complaining about it for the rest of their lives.

A lot of people really don't like Go because they have experienced other language features. Go has taken an arrogant stance at trying to make the decision about what features you might need and has given you a very small amount of things to work with.


Counterpoint, other languages - notably Javascript, Scala, PHP, maybe Java - have taken the stance that they adopt other languages' features, not because the language needs it, but because developers were clamoring for it. Which led to added complexity, because they kept adding more and more ways to solve a problem or structure an application, which led to every codebase being so different from the next that the amount of transferable skills and knowledge became less and less. Scala is the worst offender for that IMO.

One frequent praise one hears of Go is that a developer can open any codebase and immediately understand what's happening. Go is readable and stable, which are critical for code for the long term.


The one language feature that I miss in most languages is pattern matching. I wonder if there's any minimalistic language that implements pattern matching well?

And a lot of people using Go have experienced other language features as well and either decided against them or that the whole tradeoff was not worth it.

I will keep very fast compilation times and decade long backward compatibility over a lot of your features. Because those are features too.


I mean I miss some language features for sure, but the problem with adding language features is that it adds long-term inconsistency. Take a Go codebase from 10 years ago and it should look mostly the same as it would if it was rewritten in modern Go. Do the same with Java and across generations you'd go from straight for loops, to generic iterators and their for-each syntactic sugar, to for-comprehensions, streams and their functional programming style, to whatever Java is up to in 2024, I stopped paying attention years ago.

I love the fact that I can pick up a Go project from 5+ years ago and it still compiles with the current toolchain. I might need to do a 'go mod init' first.

It didn't get 67 new features that start shooting deprecation warnings on old functions in the meantime. I don't have to learn new paradigms or switch integral parts of my code to something new.

Generics has been in Go for dunno how long, haven't used it once. Didn't need to.


I used generics once, was kinda useful, but definitely avoidable. The only feature I could see myself using is something Linq-esque for slices and maps. Otherwise I’m content.

Exactly. Every programming language is a tool in your toolbox, and you should choose the appropriate one for the job at hand. For me, that's Go around 95% of the time.

I have no need to worry about a 24 byte overhead for a slice allocation, if I did have to worry about that, I'd probably use C or Rust.


And since Go is so readable, theoretically getting the core functionality out and rewriting it in a more specialized language would be fairly straightforward. And while it's an investment in time and effort to rewrite a chunk, at least you know what you're writing already.

But that's a point made in the article, that Go is also good for prototyping. But there's a few languages good for that, e.g. Ruby which powered a lot of the HN startups in their early days until parts needed to be rewritten for performance / scalability.

But writing a new, unproven product in a high performance, high difficulty language from the get-go is cargo cult; you hope you will need the performance and correctness offered by the language. Meanwhile, halfbaked hack jobs like Facebook and Twitter exploded in popularity and revenue, and while their performance issues gave their developers headaches, at least they knew what problems they had and had to solve instead of guessing and hoping.


Not sure why Go is compared to Rust all the time, whilst most appropriate comparison is Java.

I think this is exactly the right way to understand Go - it's targetted at building servers in environments where having strong consistency of code and a short ramp up time for junior engineers is valuable - i.e. it's perfect for all the big corp scenarios that Java was used for.

I think maybe the more common, but less helpful comparison of go vs rust comes from the fact that they are both part of a new wave of languages and that they both default to producing staticly linked binaries.


> consistency of code

There are many stylecheck tools that should be apart of a good stack. Accepting the creator's style is putting a lot of weight on their opinion. Most organizations have their own preferences for good reason.

> short ramp up for junior engineers

Junior engineers aren't a place you're concerned on being productive. Most of the time at that stage in someone's career they should be learning more, getting up to speed with professional practices, tools, and trying to learn code bases+patterns. Ramp up time for a language is a very minor consideration.

Both of those things have very little to do with server environments.

Bigger corporations struggle with Go's module system and forced reliance on seperate repos for separate modules. (Can you bundle multiple modules in the same repo.. yes but you're going to have a bad time)


Go is an iteration of C, not of Java.

It's a really bad choice for situations where Java is a good choice as not only is the language limited, the ecosystem around it is also very limited when compared to say Java.

I'm maintaining Go, C# and TypeScript as my main languages as that gives me excellent coverage. I'll add Rust to the mix when I have 6 months where I can accept the productivity drop or have a project where the quality requirements are lower (it only takes a week or two to pick up a language, it's learning how to engineer within the ecosystem which takes the time).


Because someone decides what language to write a new thing in is very likely to consider Go and Rust. They are very unlikely to consider Java.

Are Rust and Go sufficiently different that they should each be chosen in different cases? Sure! But that’s literally why someone would consider both and pick one.


They should consider Java though.

People have an irrational hate for it based on the enterprise cruft and horrible third party frameworks you can just completely ignore if you build a new thing

It's not good for commandline stuff but for a long running small service it is pretty great


^ this! Java's far more capable than golang and a better choice for many projects.. just don't use the decades old "Enterprise" stuff.

^ this! Java's far more capable than golang and a better choice for many projects.. just don't use the decades old "Enterprise" stuff.

..and I'm generally a Java-hater as the language itself is objectively inferior to my beloved C#.. but even then there are situations where Java is a better choice.


> They are very unlikely to consider Java.

Except they are not unlikely to consider Java.

It's a language with a very robust ecosystem, it is relatively easy to hire Java developers, and a decent job for large, complex projects.


(2022) Discussions at the time:

(130 points, 148 comments) https://news.ycombinator.com/item?id=34188528

(748 points, 544 comments) https://news.ycombinator.com/item?id=31205072


CTRL+F "rust", 24 matches, I had a feeling that would be the case. What does Golang, for the most part, have to do with Rust? I also find the following bit somewhat funny:

>The success of Rust is due in large part to it being easy to adopt piecemeal and playing nice with others.

And as if Rust itself didn't suffer from the same kind of imperfections one can find in Golang. So much for that "nothing new under the sun" back in 2012! But then it starts talking about the "rust shills" boogeyman, and one has to wonder if it's not trying to justify one's choices. (which is fine, anyway) And I agree wholeheartedly to each and every single one of the "lies" listed in the article, that you could very easily rewrite half of which to fit Rust, and the other half requires no changes to apply.


One of my biggest beefs with Go and dozens of other languages is: static typing without a sufficiently developed type system is masochism.

If I can't get at least ADTs and parametric typing, give me gradual like CL.


I love Go, so I am biased. However, the beautiful thing about Go is that it doesn't even attempt to prevent classes of bugs by making them impossible. It's a car with ABS but no lane assist, with power steering but no collision detection.

Out of all the bugs which Go permits I have yet to see one which could survive in production for some time without being discovered. Almost all of them would cause a failure the first time a segment of code is run.


Instead it is a plethora of footguns which are attempted to be managed through convention.

This post is always a scary read:

https://www.uber.com/blog/data-race-patterns-in-go/


> Evidently, the Go team didn't want to design a language.

This excerpt should be enough for you to know if you will find the article insightful or useless.


And this is why I normally start by going to the HN comments before reading an article.

I remember it took a me a bit of investigation to make Ruby call Go functions, but it wasn't really hard: https://dario.cat/en/posts/portal-between-ruby-and-go/

The argument about FFI and CGO was the most unappealing to me. If you really need to do that, it feels that at some point some decisions were made that weren't the right fit for the project.

Also, I feel that many arguments in the article are potential pitfalls for unintended side effects I've experimented in other languages, like Ruby itself. Keyword arguments are nice, but I know that they've bitten me several times.

Go forces the developer to be explicit about their intentions, that's why I also don't find something bad that your struct functions need to have the pointer receiver. It can be annoying when you forget, but that's what linters are for.


While the article raises some valid critiques, it often overlooks the fundamental tradeoffs that make Go successful in its niche. Go’s simplicity is not a flaw but a deliberate choice, enabling rapid onboarding and maintainable code for large teams. Its explicit error handling may seem verbose but ensures clarity and avoids hidden surprises common in exception-heavy languages. The complaints about ecosystem isolation and tooling ignore the fact that Go provides powerful built-in tools like pprof and dlv, which are better suited for Go’s runtime than general-purpose alternatives. Go isn’t trying to be Rust or Python—it’s a pragmatic tool for scalable, performant systems.

This. And there is one technique that would help in this.

Too often I see code like "xyz := pkg1.SomeFunc(a, b, c)" that makes xyz's type non-evident, especially when interfaces are involved.

Please write Go code like

  var xyz pkg2.SomeType

  xyz = pkg1.SomeFunc(a, b, c)
My 0.02€, YMMV.

I don't get it. Why not use a code editor that shows the inferred type on hover?

The worst thing - default values as a solution for absent values. What can go wrong with implicit value assignments?!

My biggest problem with Go is readability of some projects. In many cases, the code is split across files in an unintuitive way, and you can only navigate with the help of an IDE.

It's weird. At a time I was looking for a "better" python. Something simpler and safer than C/C++ but faster than python and more importantly that can produce a single binary. So I looked at everything from Rust to obscure language like Hare.

Go should have been the obvious choice but for a reason I don't understand I dislike its syntax. For Rust I understand: it uses a lot of special characters that aren't easy to remember and to type with a non qwerty keyboards (plus other unrelated pain points). For the different lisp, it was the parenthesis and the reverse polish notation. But for Go, I'm unable to rationalize why I don't like it.

For the anecdote, I settled on compiling my python code with nuitka. No speed gain that I'm aware of but I can now provides a binary. I'm also looking more and more at C# because of its progressing AOT compilation and although I dislike the verbosity of its base mode and the fact it's so tied to windows.

I liked a lot nim and crystal but the small community was a barrier, although I'm really impressed by what nim is managing to do with such a small community and it may me think it's an excellent language

(I will try to motivate myself to pick up one of the language I mentionned above also)


>and the fact it's so tied to windows

Dotnet Core is not tied to windows except for certain frameworks like wpf (and there are alternatives for it that work everywhere), credential store.

And it's actually really good to use these days.


Go is just an evolution of C, it works at a higher level but shares many ideas. If you can’t appreciate the simplicity of C, then you probably won’t appreciate the works of Bell Labs, and I have lost all interest in the debate.

Well, i feel like this is the 100th article i read about why golang is bad "mkay".

For my personal background, i started with golang about 6 years ago and im using it mainly for private and open source projects.

Yes golang for sure isn't perfect, but what language is tho? I think the major point is - the language you use should match your use case. If it doesn't it will always feel "bad" or you will more likely tend to find points why the language isn't perfect (for your needs).

Sure you could write a website builder in ASM or you can write an Operation System in Javascript - but why should you?

Just look at your use case - check your needs and decide what language fits the best.

If its golang? Fine use it. If its not golang, than don't. But don't take a language and try to match it on "everything" and than complain that it doesn't do the job - because no language will.

Thats my 5 cent's on this topic....


> I've started caring about semantics a lot more than syntax, which is why I also haven't looked at Zig, Nim, Odin, etc: I am no longer interested in "a better C".

Well the post rambles a fair bit, IMHO. The whole bit about Go being “accidental” is BS given that Rust is just as much “accidental” in its origin and design.

One thing stuck out to me is that Nim certainly isn’t a “better C”. It has a GC or you can use reference counting. You can use it as a better C if you really want.

Nim’s type system avoids many of the problems that Go has, though it’s not nearly as pedantic as Rust.

At the end of the day lots of software has been written and shipped in Go which runs fast, has minimal downtime, generally seems effective, and has minimal security issues. I’d argue (much) fewer software projects have been shipped in Rust. Firefox is still 95%+ C++.


>After spending years doing those FFI dances in both directions, I've reached the conclusion that the only good boundary with Go is a network boundary.

It works perfectly well with stdin/stdout as well, as seen in many LSPs


The title lacks a (2022) Otherwise great article :)

I liked the idea of a language with minimal syntax that is easy to learn and easy to understand because the code is forced to be straightforward. It didn't work out that way to me in practice.

It's a bit too low level for many use cases in my opinion, and that does get in the way somewhat. It also works against the "easy to learn" part unless you start with developers familiar with low level programming.

I also found some types of library code surprisingly difficult to read, especially when empty interfaces where used.

The standard library was great though, it covered stuff that others don't and it just worked out of the box.


> I've started caring about semantics a lot more than syntax, which is why I also haven't looked at Zig, Nim, Odin, etc: I am no longer interested in "a better C".

A strange take. Zig, Nim and Odin are about fixing semantics not to bring syntax sugar.


The article is arguing that Rust is a better choice which is laughable. Go is a small GC-ed language. Rust is an obnoxiously complex language with strong emphasis on memory management. I don’t see any projects where you could legitimately hesitate between the two.

Truth be told I quite like Go. It’s small and simple. It does the job. You can onboard people on it quickly and in practice with the toolchain you don’t run into the issue mentioned.

Would I rather use Ocaml most of time? Of course but it doesn’t have the same ecosystem. Would I rather use Rust? Heck no, I’m not going to deal with the borrow checker and life cycles if a GC is fine.


I want to build things and could care less about intellectual masturbation. It was built for proletarians like myself. I like Go because it's neither Rust nor Python. But I do understand that there are times when having some nice abstractions over common patterns doesn't hurt. At the same time, Go has come a long way over the years.

Erm..

> As it happens, I am not a junior developer, far from it. Some way or another, over the past 12 years,

I'd say that's isn't too far from it to be fair.


Okay so I’m wondering, if you’re not in the Golang universe yet, what language is better to start with learning?

Depends on what you want to do.

Go's channels are probably awesome, if you need parallel computing.

If you want to build in k8s land, you can't avoid Go.

But besides that?

Maybe, use Gleam or Elixir.


I'm curious as well. Are there alternatives to Go in some fields completely dominated by it? (like Kubernetes controllers/operators)

There's a lot of valid critique of Go, but I've never found anything like it that lets me build lasting, high quality, bug free software.

Explicit error handling means that I actually think about and handle errors. No surprises in production. No random exceptions throwing deep inside some dependency. I've been running some Go services for years with no crashes.

The brain-dead simplicity means I am not tempted to waste time being clever.

The tooling means my code is trivial to build, even years later on a new machine.


Seems like Zig is actually meeting much of the writer's expectations for good language design. Although I'm still not wholly convinced by it's move from LLVM and hell-bent desire for incremental compilation.

Should be called "Lies we tell ourselves to keep Going"

Currently onboarding to go. For me personally, it's too opinionated e.g. for: error handling, mutability, unit testing, naming conventions, lack of features like sum types.

As a new joiner, some things have a "religious" feeling, like: the go gods have given us THE language and we must use it their way, without questioning too much.

I have the feeling other languages are less opinionated and this allows for more ergonomics and comfort when developing.


I suspect it's designed that way intentionally.

It's a language built to make it hard for new joiners to mindlessly add complexity, as the language itself will fight you. It makes it hard to add dependencies that aren't very visible and vendored in, it makes it hard to change how the language behaves with overloading, preprocessor magic or macros, and so on.

It's built to fit Google specifically, with their army of new grads, and deep investment into reinventing all the dependencies themselves, and having a huge monorepo.

If you're not google, you're gonna have a bad time, which is also good for google.


So what’s the alternative then to go?

> Because they're Go experts, they know the cost of using Go upfront, and they're equipped to make the decision whether or not it's worth it

Well maybe not.

If I’m an expert in only Java 6, I might not be aware of all sorts of useful features that other languages have, such as sum types, traits, type inference… I only know one side of the trade off.

I might be vaguely aware of those ideas and dismiss them because I can’t imagine how they would fit in my Java 6 workflow.

And yet when some of them arrive in later Javas, I begrudgingly use them. Years later I can’t imagine how I ever lived without them!

We’ve seen this play out so many times.

Remember when lambdas were just for weird functional languages?


I find it fascinating the extent to which language choice makes a programmer emotional looking around at these comments.

When the article started the part "but Tailscale still uses it" I almost felt watched

I really wanted to like go, and I tried to write a discord bot using it, but the very opinionated brace style (which isn't the one I prefer to use), and the fact that I struggled to much to try and split my code across two files kinda turned me off it. In the end I just went back to python

Python has the most opinionated brace style though

You can abuse list comprehensions to enclose your code in [ ] instead of having to deal with all that whitespace.

I wish I could take the Rust pill like everyone else and be happy.

I am not a stranger to the programming world. I am fluent in Python, Java, Scala, Erlang. I can write acceptable Haskell and C++ and Common Lisp. I am well versed in functional programming (though I don’t hate OOP — it is still a good thing when done correctly, and I am a big fan of Smalltalk).

However, for some reason, I am completely unproductive in Rust. It always fights me. There’s always something I want to do (graph structures? spliced lists?), and it’s always “you don’t need that” and “it’s not the Rust way”. There’s always explicit lifetimes and you miss something and you need to rewrite huge swaths of your code. And nothing is refactorable. It’s not even a good functional language, there are no persistent data structures, and it’s a pain to make them, and no ways to express yourself functionally like I used to do with Scala.

But apparently, everyone and their dog like Rust and are insanely productive in it. What am I missing?


The superpower of go is goroutines and channels. The kryptonite of go is its limited libraries. Go is a great choice for many concurrent applications. I couldn’t finish reading the article because it lacked focus.

There _are_ two problems with Golang that I _would_ like to wave a magic wand and fix if that was a power I had.

1) Sum types (E.G. C/C++ union types) - Yeah, something similar to that should exist... it's syntax sugar over #2

2) 'casting' / reshaping perspective on data ; as long as something has the same struct size a programmer should be able to tell the compiler 'but this is actually that'. Which also implies a way of fixing the layout of a given data structure. I figure that's why Golang doesn't allow this already.

Yeah, 24 bytes (len, cap, pointer) per slice (dynamic array) has a cost, but if that really gets your goat use a fixed size array, or pointer/reference to such [n]thing.

3) Seriously, for a slice, make it like a map, a pointer/reference to a len,cap,blob - so that when I pass a slice by value I pass the WHOLE slice, not a COPY of len and cap and a reference to the slab. Current golang has the worst of all worlds with passing slices around, in that changes mutate, UNTIL it's resized or manually copied. The current design has the behavior it does to support slices of slices, which requires the pointer to the blob. A more complex scheme of a container behind the slice, and references to that could also work, but would be an entirely different datatype.


Even async in Go isn't that good, ultimately. You can't monitor channels, you can't properly react to errors, you can't shutdown and restart them. A panic in a channel will kill your program. Etc.

It's "we learned about green threads and didn't bother to learn anything else" approach (also prevalent in many other languages)


Oh well, here we go again... (2022)

The author's last post they referenced is a bit bizarre. I don't think that some overly simplified and error prone std library APIs is a particularly compelling reason to dislike a language. I didn't read the entire thing though because it was extremely long

> I've mentioned "leaving struct fields uninitialized". This happens easily when you make a code change from something like this:

Really? Are you not using gopls? It absolutely will warn you about this. And the mutex case. And a lot of the other similar criticisms.

> Go not letting you do operator overloading, harkening back to the Java days where a == b isn't the same as a.equals(b)

In languages that have it I've never used operator overloading to produce anything good or that was obviously better than just using methods.

> The Go toolchain does not use the assembly language everyone else knows about.

Honestly I've never noticed or had to care. And you can build plenty of things that don't require CGO.

The whole "gnostic" tone in language articles on HN is becoming overbearing. There are parts of the language to criticize but couching it in this "lies _we_ tell ourselves" and somewhat kitchy writing that presents minor matters of taste as fully existential issues makes this an unsatisfying read.


> In languages that have it I've never used operator overloading to produce anything good or that was obviously better than just using methods.

I used a Scala library for S3 once that overloaded + to mean upload.

    var bucket; var file;
    bucket + file;
Which is obviously bad and unnecessary.

It's really a feature that should be used very rarely by those who make basic libraries, but there it can make a lot of sense - data structures, numeric types, etc.


That says more about the library design more than it does about the library.

There are much better examples of the operator overloading for example cons:

item :: item2

Later you can break that down in pattern matching.


This is a silly article, though I can only speak for myself. First of all, language design is pretty much the last reason anyone should consider when choosing a language. For me as a solo developer who needs to get results in time, the main criteria for choosing Go were compilation speed, development speed, general tooling, and third-party support. Especially the latter is extremely important; I cannot develop all kinds of libraries like e.g. an Excel or Word document parser in-house, I have to use whatever MIT-licensed libraries that are there.

I've developed in all kinds of languages in the past: Pearl, Java, C++, REALBasic, PowerMOPS (a Forth derivate in the 90s), Ada, Rust, Python, Java, Racket and various scheme dialects, CommonLisp, ObjectPascal/Lazarus, just to name a few. Out of these, REALBasic was by far the one in which I was most productive. Alas, it became unaffordable and the vendor lock-in really burned me. No more commercial languages or IDEs it is for me.

If Ada had a garbage collector, I would probably use it even though the development time is a bit longer. Likewise, I'd love to use CommonLisp and think it's the best dynamic language. But it simply doesn't have enough 3rd-party support, and I'd also be wary about how it runs on mobile platforms.

I've got to say I'm pretty happy with Go so far. Is it the ideal language for me? No, because full OOP with multiple inheritance would make my life much easier. But it works, is easy and fast to develop in, and the results speak for themselves. I have no problems with bugs. The explicit error handling may be a bit cumbersome but it forces you to think about and deal with every error, unlike people in languages with exception system who often ignore errors until so far up the call chain that they don't even know what actually happened any longer. I don't see the point of that. If you have an illegal state of an object it doesn't matter if you call the object nil or DefunctInstanceOfBla, you're going to have to deal explicitly with the illegal state.

Notably, C# was also in my final selection of languages. For my principal use case - distributed client/server applications with GUI - Go's GUI options were not so stellar and I was thinking about using C# instead. AFAIK, C# is very suitable and a great language, too. I decided against it because of the C#'s bizarrely complex runtime library situation and naming schemes (WTF?) and simply because I would have had to learn it first and already knew Go fairly well.

Beware the language aficionados and purists. I used to be one of them, too, advocating Scheme & CL. However, in the end purely practical considerations are always more important. Programming languages are tools for getting things done.


“An idiot admires complexity, a genius admires simplicity, a physicist tries to make it simple, for an idiot anything the more complicated it is the more he will admire it, if you make something so clusterfucked he can't understand it he's gonna think you're a god cause you made it so complicated nobody can understand it. That's how they write journals in Academics, they try to make it so complicated people think you're a genius” ― Terry Davis

Please note, no offense intended, I just like this quote to describe Go success.


The article is great. The bigger picture, of course, is that it’s always “pick your poison”.

On one hand, say, I love operator overloading, I love how Python does it (once you satisfy an interface, its operators Just Work).

On the other hand, I can appreciate the choice not to do it at all because half of the ecosystem will do it, and another half won’t. Also, it would require implementing function overloading, and it is a can of worms.

Or generics and rich type systems, which all come with their own tradeoffs. I hear that Rust cajoles you into tinkering with the type system, and wach tweak requires refactoring of more codebase than anyone would like (don’t take my word for it, it’s just what I heard from a few different sources). I know that Nim is so expressive that it can be annoyingly trivial to be too clever and run into a valid edge case that will make the compiler barf and die. Go sidesteps the issue by not wading into that territory, and that may be perfectly okay, albeit verbose.

It’s always picking your poison, so I guess check your tolerances and allergies so it doesn’t kill you before you get the job done…


> I love operator overloading, I love how Python does it (once you satisfy an interface, its operators Just Work).

Is this different from other styles of operator overloading? Why does it matter whether, when I want to overload the + sign, I need to define a function called `__add__` or `operator+`?


For me it's easier to introspect, fewer unexpected corners. Contrast this with operator overloads which you can put literally anywhere.



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

Search: