> if err != nil is the feature, not the bug. It forces you to look at every place something can go wrong and decide what to do about it.
No it really doesn't. It litters your code with if statements that are all just about the same, except that one that needs to be different, and you go blind looking at them all and can't spot the difference. And these days people probably just type "tab" and their LLM assistant fills out the entire block incorrectly in one keypress copying the pattern from everything else.
But LLMs didn't create that problem. Having to type something never meant you had to think about it, or thousands of "sudo shutdown -r now" commands would never have been run on production databases, because typing "sudo" would have magically made someone think about it, rather than just being keyboard memory.
And the problem of reviewing the code and spotting the one error handling block that should be different from all the others is always going to be there for human reviewers.
Rust converts the common case boilerplate down into one character: ? which lets you focus on any exceptional error handling rather than a wall of if statements that almost all look alike. And the compiler can see that you're ignoring a Result from a function call and force you to explicitly do something about. Plus you can then use a monad without knowing a single thing about monoids, endofuctors or category theory, and impress your friends.
> No it really doesn't. It litters your code with if statements that are all just about the same
Wrong thing to be bothered with. This allows static analysis of every possible path the code can take. Try to do that with throw/catch. There is a reason many industry guidelines like misra, jsf, etc just outright ban hidden paths. They have been literally catastrophic. There is a reason why many modern languages like go, rust, etc, want errors to be explicitly handled.
> Rust converts the common case boilerplate down into one character: ? which lets you focus on any exceptional error handling rather than a wall of if statements that almost all look alike
Again, wrong way to look at errors. Errors are not a problem for a compiler to resolve. Errors are part of your business logic. They need to be explicit and handled. The syntax of one language vs another is not the point of error handling. Errors are not a distraction. It is your job to review each path the code can take.
Code may be written once, but code is always reviewed or audited or referenced more than once. No one is impressed if you write some meta magic one-liner. I would rather be more impressed if I can read your code and form a mental image on how many paths your code takes to achieve an outcome in a single sitting, instead of pinging you for a quick call to explain yourself.
> you go blind looking at them all and can't spot the difference
> And these days people probably just type "tab" and their LLM assistant fills out the entire block incorrectly in one keypress copying the pattern from everything else
Now, if someone is 'going blind' looking at error checks, its because their function is doing too much and has too many failure points. Perhaps one may need to focus on honing their software engineering skills on how to structure their code, instead of hitting that 'tab' more so often.
Go has plenty of hidden code paths in defer, panic and recover. You can minimise the use of those things, just like in other languages you can minimise use of try-catch in favour of a Result sum type, which is already better than Go's "multiple return that you have to pretend is a sum".
Which is what should be discussed about go, rather than if-err clauses. That is hardly an issue in go. But Go has these little quirks which, if not careful, can become a problem. But instead most go critiques discuss only the error handling syntax. Look at the other comments.
The article is on point on the strengths of go, and in many cases, those other languages you say, you will find it really hard to do the same thing with the ease with which you can do in go.
What the article glosses over, is the footguns with working with the language. No compile time nil checks, implicit interfaces, lack of sum types, like you said, which more so often leads to bloated structs, member visibility which is just private and public. You can find more.
But these problems were not the main focus of go. Go was build because google devs wanted a simpler way to manage and deploy their software across thousands of services, not having to wait forever for their code to compile, not worrying about writing cpp code in the BEST possible industry standard, ability to do parallel processing of hundreds or thousands of workloads without choking your cpu, managing dependency hell. These were actual roadblocks. And google was not the only one facing these problems. Go solved all of it. Which language was solving this before go?
Go was developed because two UNIX guys, and one Oberon guy, that didn't like C++ decided to create their own thing, and their manager decided to support them as their 20% project.
Nothing else.
The narrative of Google wanting Go never happened, it gets sold as such by many Googlers.
Even Kubernetes was originally written in Java, before some Go folks joined in and drove the rewrite into Go, which besides the Android build system are the only two key projects using Go.
Most of that was written by the Go team, not Google management, and even has the famous Rob Pike take on it.
The You Tube download rewrite from Python to Go, wasn't a management decision, rather yet another case of Go team taking language marketing into their own hands.
You will find posts from me on golang-nuts pre-1.0 days, as I was hopeful it would turn out differently back then, so I don't need education on how Go came to be.
> Most of that was written by the Go team, not Google management, and even has the famous Rob Pike take on it.
I am unable to understand your point or you seem to be making arguments that have nothing to do with the article nor with my earlier replies. I wonder why. Are you expecting sundar pichai to announce golang to his shareholders in his quarterly call? Which language does that?
This article and my replies are technical in nature. You need to have a technical response to a technical problem. You cant get emotional everytime. Which language allows me fast builds, lightweight and high performance concurrency, strong stdlib and tooling, easy cross compilation with static binaries, simple syntax, decent performance and by that i mean not worrying too much on memory management, easy onboarding and large scale maintainability? I am willing to try that tool.
Because your narrative is wrong, it wasn't a Google project, it was a group of Google employees that rebeled and were lucky to have their line manager support, given being UNIX idols.
Google upper management was, and is, perfectly fine with Java, Kotlin, C++ and Rust, for the most purposes.
I won't suggest any "technical" answer, because clearly you only want Go.
> No one is impressed if you write some meta magic one-liner.
I am very impressed if they can express the entire algorithm succinctly with a one liner. That's the kind of abstraction every piece of code should strive for and be shamed to the ground if they haven't.
> I would rather be more impressed if I can read your code and form a mental image on how many paths your code takes to achieve an outcome in a single sitting, instead of pinging you for a quick call to explain yourself.
Does this mean you're impressed by the ability to read go's if spaghetti?
> if they can express the entire algorithm succinctly with a one liner
> That's the kind of abstraction every piece of code should strive for and be shamed to the ground if they haven
This obsession with one liners is inversely proportional to professional experience.
I agree with all the motives you describe, yet come to the exact opposite conclusion.
The overwhelmingly common case is for an error in a nested call to be bubbled up to be handled by a parent call. If you make this common case look similar to the distinct case of actually handling an error, you just obscure where the real error handling happens.
Writing good error handling is Go is really hindered by two other issues mixing together:
1. There’s no Result type, so while it tries to treat errors as values, it’s missing out on a lot of the benefit of that idea.
2. Multiple function return values are implemented as a special case, and aren’t themselves a value
Most languages that support multiple return values, do it via some notion of tuples: returning a single aggregate value that you can pass around as a whole, but that also have some nice syntax for destructuring them into local variables. Go implements as a syntactic special case.
You can’t assign the multiple return values of a function call into a single variable. You’re forced to take all the parts, and pack them into a struct yourself. This means that you can’t factor your result-handling logic into testable functions, without needing to do this dance at every call site.
> Wrong thing to be bothered with. This allows static analysis of every possible path the code can take. Try to do that with throw/catch. There is a reason many industry guidelines like misra, jsf, etc just outright ban hidden paths. They have been literally catastrophic. There is a reason why many modern languages like go, rust, etc, want errors to be explicitly handled.
I mean I'm explicitly supporting how rust does it over go, so I don't understand how this is any kind of rebuttal. In your last sentence you admit that rust enforces that errors are explicitly handled, and I think it does it much better than go does, and does it without littering the codebase with boilerplate if statements. The fact that you're launching into an explanation of the insufficiencies of throw/catch exception handling when that has absolutely nothing to do with what I was arguing... Well, I don't feel the need to argue with you any further. Rust also has errors-as-values and doesn't throw (at least not the common case, just like go), so the go FAQ on exceptions isn't relevant to the discussion.
And I'm just going to end the argument with that, since you seem to be a waste of time to deal with.
> No it really doesn't. It litters your code with if statements that are all just about the same, except that one that needs to be different, and you go blind looking at them all and can't spot the difference.
If most of your error handling is just bubbling the error up, you are doing something wrong.
if err != nil { return err; }
is an antipattern. Not because it's verbose, but because you are supposed to handle the error, not pass it right through, in most cases.
There's a lot of merit in this. I call Go the Honda Odyssey Minivan of the programming world. It doesn't do anything exceptionally well but it does lots really well and in a way that's simple and reliable. Especially for the backend serving react front end niche.
But it's also a pig to write and comes with a lot of foot guns. Especially the Null handling. Somehow they made it worse than every other language.
It sets primitives to 0, "", false etc. Which is almost always but not always fine. And if they're complex objects you still get NPEs
To get true nullable fields you need to use pointers. That's a whole topic in itself but they're awkward.
It's much worse than true nullable objects that your compiler can check for NPEs. It throws fewer NPEs but at the expense of data integrity where you don't know if your 0 is actually a 0 from the user or a null.
What if Go went all the way? Referencing a zero pointer (nil) gives you the zero value of the pointed to type. If you try to access a zero map, it tries to deference the zero pointer to the underlying buffer. The zero pointer gives you the zero slice with zero length. The presence check fails without crashing and you get some pretension of reasonable behaviour.
Well, what happens when you write through an invalid pointer? The runtime can swallow the write or crash the program or allocate new storage and replace the pointer. Depends on the wanted semantics.
Silently returning some (the wrong) value is always worse than catching the error right then and their and panicking. A panic is a noticeable symptom of something just having go wrong that is easy to chase after and debug. A silently returned wrong value just causes data corruption down the line, at great pain. It is very annoying to debug, in particular if people start to some times rely on this behaviour as an intentional part of their data flow.
I agree. I prefer Optionals over Nulls in any case. This was just a thought of what if Go went all the way instead of just halfway. In my ideal language, you would have explicit assignments and compiler checked (runtime checked if absolutely necessary) pointer validity (with allowed length) at construction. As far as I know, the only time you need to make a pointer and say "trust me" is when doing MMIO so that can given its own mechanism. If it exists in the program state, it's valid.
I think there's a large subset of programmers now who consider null checking (or even the existence of null) to be bad, and prefer something else like exceptions or option types. I don't get it personally.
The existence of null is not the problem, it’s when null populates every single non-primitive type, making every access into a logic bomb unless explicitly checked. When null is a distinct type, there’s no problem at all.
Some weird nils don't compare equal to other nils. It's surprisingly easy to construct the weird kind by accident, because the conversion is automatic and invisible.
I like go, but a lot of little things stop me from loving it.
Like, enums. I get a lot out of the box when I use an enum in Java or Kotlin. Converting to/from a String is trivial. Type safety ... exists.
I can do that in Go, but I have to hack it in, for every single enum type I want to represent. Enums are not a thing in the language, which means its easier to keep the language in your brain all at once, but at the expense of making it harder to keep the software I'm writing in my head. Is this "enum" the same as that "enum"? I have to go read the code to figure it out.
But Go is excellent at a lot of things. Compile times, static binaries, resources compiled right into that binary, execution speed ... there is a lot to love.
The anti-exception mind virus of the 2010s did a lot of damage. Go designers finally caved and added panic & recover, but jeez, the damage was done. The whole ecosystem has exception derangement syndrome.
Go had panic/recover right from the get go. Nobody “caved.” And indeed, you are free to use panic/recover for error handling in your code. That was always allowed.
That said, the key insight of the exception craze is that error returns are a normal part of a functions behaviour and as such should not use extraordinary control flow to take place.
Exceptions (panics) are used for things that should never happen or are indicative of a programmer error, like nil dereferences or out-of-bounds array accesses. That is, things where the programmer is not expected to provide reasonable behaviour on the API level if it happens (but perhaps there is a whole-program fault handler to shut down cleanly).
Opening a file that does not exist? Database record not found? Invalid credentials? These should be anticipated by the programmer and can occur at any time. Not exceptions, normal return values. Go requests that you think about these possibilities instead of pretending they can be ignored.
You're right about recover being in 1.0 of golang.
I definitely fully understand the trade-offs of returning result/error unions vs handling thrown exceptions. Exception handling is clearly superior to me. That said, the typical performance complaints made against the most common implementations of exceptions are valid.
Since AI coding, I've switched 90% of my code to Go. It's really great for most things. Lacks a development community large enough to have a really solid UI framework, but the existing frameworks are "good enough". I used AI to make an AI agent that works on Windows, Linux, Mac, iOS, and Android, with CLI, GUI, and web serving. LOC: 5575. Binary size: 35MB.
Also: why can't we vouch for flagged stories now? This post is actually good, and funny, and the conversations are worth having.
I love Go. But I prefer .NET for web development that also compiles to a binary and has a great ecosystem of libraries and packages. Go is great if standard library works (and it can for many cases) but when you need to start looking into non standard libraries, Go can hit limitations.
For example, to build a full production web application with database in Go, there is no great out of the box migration tool. There are some good 3rd party libraries of course but compared to something like EFCore in .NET, they don't come as close.
For me, it is now .NET and then Go. Of course, I use Go when just doing a lot of non web stuff as well.
Eh, at least for people consuming .NET apps compiled with NativeAOT, the experience is similar. Applications can be compiled as a single file with no dependancies. A hello world in .NET is half the size of one in Go:
"compiles to a binary" is not a useful criterion. The criterion Go is winning on is "compiles to a single, completely self-contained binary," meaning it does not depend on libc or any external runtime. You can't say that about .NET. You can't say that about damn near any other programming language. It's extremely rare. The fact that .NET uses a binary packaging format is, like... well ok, so what?
Oh that's good to know. I happen to think Microsoft is chasing a bad philosophy with that declaration, and there is no more danger in statically linking ssl if you're continuously rebuilding and deploying your statically linked scratch image, but then again, the Microsoft approach to a lot of things isn't what I want in my datacenter. To each their own, I guess. I happen to love how self-contained Go programs can be and do not consider that a liability but a strength.
Lol well I don't know what the trigger is for pulling in libc there, because I've built massive scale services that did a lot of nontrivial stuff and then the deployment was a single-binary docker container that did not have libc. The only thing needed to be put in the container was a directory full of root certs so it could do TLS.
(full disclosure, I don't think I ever had my service look up the address of localhost)
edit: seems like you probably have CGO_ENABLED=1, which is now the default and will cause simple networking things to use libc. Set CGO_ENABLED=0 and you won't have libc.
I suppose you are missing the point. Go had support for self contained binaries since its introduction in ~2009. This was not the case with dotnet, if i am not wrong.
But nevertheless , if any language provides something like that, win-win for entire ecosystem.
Agreed. The fact that .NET has a way to do true static linking is great and I didn't know it existed. The fact that they apparently think it's a weird and undesirable thing is less great and would make me worried that they're going to undermine the ability at some point. Golang has had this ability since way back and they think it's a strength.
I can see reasons why people don't want to use .NET if Go is available. .NET has its merits but it's bloated, compilation is slow, and I find it's tooling to be really annoying.
For me go is just above c# and both of those are not super high on my list.
I've never understood what is meant by "bloated", would you mind explaining, so perhaps I could better understand?
If it's "Large standard library", I think that's a selling point. Having anything you need available ( although these days, via optional microsoft.* packages ) helps keep projects consistent between different places.
If it means "Different ways to do the same thing", I can understand that criticism better, and some of that comes with 20+ years of legacy, where the wrong thing was done, and now there's a better way, but a ruthless adherence to backward compatibility means that the old way isn't dropped.
The .net sdk is like 1gb, go is like 60mb or something. It's annoying when that matters.
More so, nothing happens quickly with it's tooling, and the tooling isn't friendly. All of a sudden I can't build my project in vsstudio(also bloated but admittedly optional but may be required depending on where you work). Clean build also fails. So I have to go to the command line and type in dotnet restore, dotnet build, magic it works now. ???
Okay time to install a package because msft has its own packages for things like aspnet but now I want to serialize json. Cool newton soft sounds good. Ah now I need a package manager, I'll install nuget. Oops I need to tweak this weird xml file with lots of options.
I press compile I wait 3 minutes to realize there is an error. Okay it builds and looks good in debug mide now I want to send my application to a friend who doesn't have .net runtime...
With go it's more like. Okay I automatically have web access from the std lib. I want a framework oh I already have a package manager. Edit my toml. One command done. Time to compile, poof done in 2 seconds. I send the binary to my friend and they run it.
I understand some of these are 1 time cost things, but I am requesting you to read between the lines as these aren't examples I am trying to quibble over. The point is the friction that go has focused on removing by being quick and small. It has less legacy cruft but I tried to ignore as much of that as I could.
Also keep in mind j am not a big go fan. It's not my favorite language but it is way easier to deal with on a regular basis for me
That's quite a rant, but it seems like it's from experience with the old times. For the last decade or so it's been a completely different story.
System.Text.Json is part of the core standard library, the only reason to use Newtonsoft is developing on legacy applications.
Visual Studio isn't necessary or even much recommended any more. VSCode or any LSP editor works fine.
You don't need to install nuget, any more than you need to install cargo in rust, it's part of the SDK. You can also just edit the csproj in the same way you could edit your TOML file. It's XML, and you just add <PackageReference="" />.
You can compile --self-contained to be able to run it somewhere without the runtime installed.
.NET has a long history, but almost none of these are actual points of friction in the current year.
I disagree as someone who was using dotnet within the last 2 years.
Also we are comparing go to .net not to rust. Go is much easier and faster tooling wise. It doesn't require an IL and doesn't have 2 decades of enterprise cruft with vendor lock in strategies baked into it.
I asked to read between the lines.
The experience behind each of these things really stinks compared to go. If you haven't tried it, I recommend it just to feel the ergonomics. I don't care for go as a language. The funny thing is, I find the tooling so much easier, better, faster than .net that even though I like c# more than go as a language, I would prefer to start a new project in go instead of say c# instead. It's that annoying for me. I've met several other people who are language ambivalent who feel the same way.
I could write a huge blog post about it with facts. I'd rather not though. People do what they like and I am not here to change what someone likes.
Every time I see those kind of posts, it seems reasonable on the surface. But then you remember than a typical web app might want to do something beyond just serving an HTML template.
Like, what if you want some styling or javascript along with your HTML? Either write everything by hand like it's 2003 again, or start inventing a complex pipeline from scratch in Go. Countless of people have spent thousands of man-hours on solving this problem, but you use Go, so now you do this yourself from scratch.
Or maybe your app needs some relatively complex database access? Writing SQL-spaghetti only gets you so far until you need to do conditional joins and unions and parameterized subqueries etc. God forbid you want migrations too. Go has no answer for this and you either have to hand roll all those things as well, or spend tons of time comparing countless libraries that do some of things you want, but not quite everything, or don't fit into your pipeline for whatever reason.
I'd take an opinionated framework with fat dependency stack that has way more features than I'd ever need over trying to reinvent migrations or CSS preprocessing from scratch in Go, thank you very much.
I often think of go as a "better" python. As in, easy to learn and easy to use. But also performant and the module system and package manager seem to be a little neater. (sorry for flamebait)
But I wonder how well it can cover similar use cases? Go is great for devops and web backends. But what about AI and data science?
You can just embed the C library into the binary of the Go app call it directly in Go. Most of the time I have found calling C from Go faster or on par with calling C from Python.
I used to do lots of data engineering in Python, then started doing all kinds of engineering in primarily Go.
The Go ecosystem for data is very limited. There are no widely supported dataframe libraries (like the og pandas and the newer polars written in rust and also available as a crate). Very few data science libs, a few decent gen AI libraries, but not as popular as their Python cousins.
Most of the work I do now is streaming data and very small batches. For that Go is amazing. I don't need dataframes to transform a json, combine it with a bunch of other data and write to a database. I just need to write that logic and make it go fast. Very easy in Go.
Yeah, it breaks when the author decides to move from Github into Gitlab to protest against Microsoft.
Time to update all code references to Gitlab across the globe, in every single Go project.
Or spend the time configuring redirects between URL mappings, across everything that depends on it.
Not to mention that except for lacking garbage collection, even Turbo Pascal 7 for MS-DOS was more modern in language features, with faster compile times, on a 20 MHz powered computer.
> Yeah, it breaks when the author decides to move from Github into Gitlab to protest against Microsoft.
The import path is disconnected from where the build system looks for the files. Of course, the default is to use the import path as an URL, right, but nothing forces you to do that. And if you do, *you* lock yourself to the hosting you use. But that can be hardly blamed on Go.
tl;dr: Serve this at your domain, update the repo-root when changing host platform for your code: <meta name="go-import" content="import-prefix vcs repo-root">. No one else has to do anything.
"This is getting really fucking irritating. Every 3rd comment on every HN post is "This is LLM", which has become a proxy for "I dont like it so it must be llm"
Yeah, I suppose I am in the minority here, but I still judge a posts on their merits.
LLM witch hunts are tiresome.
(Perhaps the worst part too is that an accusation can be leveled and there's no way to counter. If it's a vacuous fluff piece, say so—irregardless of who/how it was written. And there we are judging a piece on its merits…)
Yeah I didn't flag it myself. I skimmed it and I didn't get an immediate AI vibe, like someone else said it seemed to be trying to use the motherf*in website style
Unlike witches, LLM content exists. And worse, it has proliferated to a point where you are more likely to spot LLM content in the wild then human written content.
Witch hunts are bad because they target innocent people and burn them at the stake. When the whole internet has been filled with LLM content, it is not unreasonable, expected even, that you start accusing everything of being an LLM, because, most likely, it is.
I love go, but I find it did little to make concurrency management easier to reason about. Race conditions are easy to write. Go routines have all the same concurrency problems of threads.
In the parallel HTTP fetcher, the error is discarded. This will likely result in a panic when the response is nil. Also, what if it a server locks up? Or the underlying socket never connects and never times out?
I know it’s a toy example, but one must consider all these things in a real system. Go does have good pathways for these concerns, but it’s also easy to do it wrong. I still have to manually reason about access to variables/struct fields from multiple go routines.
I find I have to design servers from a data flow view; when my performance demands let me do everything via channels to worker goroutine pools (including sending a response Channel back to the request handler) I don’t have to do any locking at all. I find when I have to add in mutexes it means I haven’t thought enough about the problem (or I am fixing some hot spot from the profiler, rarely).
Its absolutely laughable that Gophers act like their language solves concurrency when its actually worse than Java because it has data races. Go added nothing and moved backwards.
The language that actually solves concurrency is Rust, but they will never acknowledge that.
Now that LLMs can breeze through the Rust boilerplate there's no reason to ever write Go again.
It's one of the dullest, most mediocre languages out there and despite a nice toolchain and the fact it's undoubtedly a "safe" choice, I just have zero interest.
Someone has to review the LLM generated code. If it's not the developer, it's the SRE at 2 am. For someone without significant experience, it might be harder to review Rust than Go.
Not sure it's harder. Rust type system makes it much harder to shoot yourself in the foot with nils and errors, at least. Biggest issue is probably the number of 3rd party dependencies every non-trivial Rust app ends up needing.
I guess other languages that follow a stricter/larger compile time regimen e.g. Haskell, OCaml, Idris, ATS, F* would be suitable as well for this kind of approach. Like Go, OCaml seems to offer low compilation times.
I bet if Rust was more mature back then, the Python to Go rewrite from Docker, and the Java to Go rewrite from Kubernetes would both have been into Rust instead, and that would be it.
I can write go but I don't prefer it. It's ... okay.
Things I dislike:
- if err != nil. Just give me some syntactic sugar instead of letting me write the same thing a bajillion time.
- no way to bind a struct to an interface. I'd like my IDE to tell me when I accidentally stopped implementing an interface
- some stdlib parts are too bare bones. Unpacking an archive requires me to handle all files, directories, links, etc. myself. There is no move command that can move a file or directory across fs boundaries. The little things.
In my experience only when you implement it correctly. If I add a new method to the interface I can't navigate to the implementations anymore because they don't completely implement the interface.
It is. But it's also an inconvenience. In a language like Java I can just say that a class implements an interface. If the interface changes the compiler and the IDE tell me about the missing methods. Go not so much.
Now your compiler will tell you you stopped implementing the interface. Pretty? No. But it works. And gopls will even offer to implement stubs for missing methods.
This is a useful hack but just imagine someone coming from another language reading this. They'd have no idea what it's for. It looks a bit like the things C people do to themselves. And all just because someone didn't want a keyword or another form of annotation to signal that a struct should implement an interface.
These are the small things that annoy me about go.
EJB, Spring, Ant, Struts (I'm getting old - Like Hot Java Alpha 3 and Java applets old), maven, pom files, etc.
I used to love Java but the complexity merchants showed up and ruined the party. 1.5 was just coming out when I stopped doing Java dev. Kotlin might pull me back into the fold though for when I can't use Go.
The good stuff landed in Java 8, so you left the party too early. From Java 8 on it has changed direction completely (less OO + JavaBeans, more functional + ADTs). It feels like a completely different language from the Java 1.4 / 5 days.
You can write apps without that stuff? Sure you should have 1 dependency management system, but otherwise you can write clean JVM apps in many languages, without DI or much else.
That was a long and dark time in history. We're still in the shadow of it. But these days a much better world is available - a more powerful language, good libraries, and much, much simpler frameworks.
To be clear, all the annotation and Java EE stuff is still going if you want it!
My big issue with Go is, the language just isn't that great. Zero values instead of sum types, reflection instead of proper macros, a mediocre module system…
Java's warts are far worse than Go. Everything is nullable. There's no module system to speak of. It's so IDE-dependant.
I agree with the spirit of "use boring technology." So thank god Go is boring enough that I don't have to write Java anymore.
Only reference types, the same as in golang (which also has nullable pointers, and its interfaces interact weirdly with null). Java is getting value types, which can be declared as non-nullable.
Everything's a reference in Java except primitives, and even primitives get object wrappers. In Java, String, Long, and Bool can all be null. Go isn't like that—only explicit pointer types and interfaces can be nil. In practice it really cuts down on NPEs.
Ok, granted, but I have never seen these in actual use. Java's ecosystem is big enough that you can use Java for years and not even know it has modules. I find this profusion of features unboring for the same reason that C++ is unboring.
In Go, everything's already in modules. It's just simpler. And when they did add generics, it was backwards-compatible, so there was no Java 8/Java 11 thing.
> Furthermore, any non-trivial project is better served with an IDE
In any other language, I can get by fine with vim + LSP regardless of project size. Java has a uniquely bad LSP story.
> `if err != nil` is the feature, not the bug. It forces you to look at every place something can go wrong and decide what to do about it
Haven't really used Go, but can't someone just `result, _ := foo()` and go on using `result`, not checking any errors?
The way Rust does it seems closer to forcing you to handle any errors in order to obtain the result (though it is still easy to just `.unwrap()` without properly thinking about it).
We do want to check for errors, we'd just prefer that it not be a repeated 3-line boilerplate pattern that ends up being >50% of all code. Rust does it with one character.
I agree that Rust's approach is better. I'm questioning the claim that Go "forces you" to handle errors, since to my understanding with Go someone can just eschew that 3-line boilerplate, silently ignoring the error, and still use the result (which is bad).
Silently ignoring errors by leaving out some boilerplate doesn't really seem like an active/forced decision, or a selling point over the languages it disparages ("[...] hellscape doesn't make errors disappear, it just hides them"). Then that the correct path is the one of more resistance seems poor design, in my surface-level opinion.
It is a decision though, and one you have to consciously make for each and every occurrence. You can't just hope some catch block will eventually handle it...
I may be missing something, but I don't see how just forgetting to handle/propagate the error (and, say, causing data corruption by continuing with whatever empty/partial return value the function happens to give) would be a conscious decision.
Even when it is intentional, like writing some quick/dirty code and planning on handling errors properly later, I'd imagine it's difficult to grep for instances of unchecked errors in the way you can with `.unwrap()` - though tooling should help.
Personally, I don’t want to swallow errors I want to throw them. 90% of the time, I have a few small cross-cutting points (like an api controller) I want to catch and handle exceptions that get thrown from the rest of the code base.
After having way too strong opinions about this, and even if Go is my go to language, I've stopped caring for this kind of statements.
In the end, the language doesn't really matter and extremely rarely is the bottleneck.
Python, Java, Go, C#, PHP, Node, Ruby or Scala, for backend stuff, all these languages can do the work 99% of the time. They all have well developed ecosystems, and are sufficiently mainstream to easily find developers.
In the end, these languages are just variants of the same tool, and I find more interesting to just pick one and build something with it, rather than bickering endlessly about which one is better.
The single executable deployment hasn't been an advantage for a very long time. C# has Native AOT. Java/Kotlin/Scala have GraalVM native-image, plus Kotlin Native and Scala Native.
And of course there is Rust, which compared to Go has better performance, lower memory usage, smaller binaries, safe concurrency, safe resource handling, and a real type system with tagged unions.
The Go team built good tooling around a mediocre language. Now most other languages have caught up, and Gophers are left writing mind numbing `if err != nil` checks with no benefits left to show for it.
If the toolchain crashed while compiling, please file a ticket! That shouldn't happen ever and if it does it is indicative of a big problem that needs fixing.
Not mentioned is that Gemini does a pretty good job of writing Go in my experience of using it to generate utility scripts, and a friend’s use of generating an internal website for using a corporate API.
I've typically leaned towards Python for my agentic programming, because the LLMs have been good at it and I'm familiar with it if I need to take a look. But I'm just finishing up an apt-cacher replacement and decided to use golang and the experience has been really great.
I'm using CC+Opus 4.7 max effort, and it's produced a working apt cacher from the first phase of development, so far there have only been a few things I've had to ask it to fix. This is over ~52KLOC (counted by "wc -l"), going on day 3 of it working on it. This includes: caching proxy, garbage collection, "http://HTTPS///" kludge (apt-cacher-ng semantics), MITM https proxy, admin website + metrics, deep validation of metadata and rejecting invalid updates, snapshots of upstream state and delayed metadata update until "hot packages" are available after metadata update...
10/10, would go again.
FYI: My agent loop is: "Work on next step, have codex review it, compact", and then a couple rounds at the end of a phase to review the code against the spec, and a couple rounds at the beginning of a phase to create the spec.
I start out writing most of my terminal applications and utilities in Python but when something hits a performance ceiling I convert it to Go. That's been a pretty good bar for when it's time to use Go for me and so far so good.
You cose something and if you don't use some weird 3rd party packages (Go stdlib is quite complete) you can check that code again in 3 years and will still work.
In seriousness, Go is a good choice. Or at least it’s not a bad choice, I’d definitely pick go over many other languages. If go had a better type system, it would be damn near perfect.
Maybe some common complaints about Go are finally less of a problem in the current coding agent era - e.g. ecosystem weakness complaints and verbose error handling.
Though TypeScript's type system is maybe still more powerful - and therefore might have the edge for agents writing code? (Not to mention there's probably more TypeScript in the training data for LLMs, though perhaps there's _better quality_ Go - I'm not a Go dev though so I couldn't comment further on this.)
I got turned off of Go pretty fast by GOPATH shenanigans and polluting my home directory for no reason, since I don't think a programming language should really have any say on my filesystem.
Error handling also seems pretty dumb in comparison to Rust. Admittedly Rust is a much more complicated language, but I felt like I could just go learn more Rust instead of bothering with Go and have more fun.
Like, if the gripe is that Go should be better about following XDG conventions, fair enough. But pretty much every language other than C, C++, and Node tends to use system paths for transitives/compilation artifacts/registries/downloads.
I have been using Go since 2014. I have services that just run without issue.
Having backwards compatibility with 1.0 just makes it easier to maintain software.
The big plus in the modern era is that the simplicity of the language lends to having agents write Go without much fuss. That and the standard library being batteries include lets you direct the agent to use little or no third party dependencies.
I should write one of these for Haskell. Huffing abstractions is great for boring, line-of-business applications.
Goroutines? Meh. Software transactional memory and green threads? Heck yeah.
An actual type system? Chef's kiss.
Scott Wlaschin from the F# world has written and talked extensively about F# for "boring" software. It works equally well in Haskell. You don't need to use type-level meta programming to spit out a basic service.
Just fucking use the language appropriate to the task you're trying to accomplish.
Just fucking use the language your colleagues can understand and support for the next few years.
Just fucking use the language with the framework and tooling you need to get your job done efficiently and effectively, and one at the appropriate level of abstraction for the project.
Just fucking use the language which AI agents can read and write well, because we're in the End Times and this stuff matters.
Just fucking use the language with great testing and CI/CD support because you'll be spending longer supporting your code than writing it.
The skill is choosing well, and a key realisation is that it's never a one-size-fits-all thing. That's why an article like this is less than helpful, gosh darn it.
Citation needed. Go's fat binaries and big stdlib cause most enterprise-mandated CVE scanners to light up with zillions of false positives constantly, because too much shit is present in the binary.
Logger package technically could speak protobuf over gopher, even though you use it to write text to syslog? Congrats, gopher and protobuf ecosystems are compiled in, with their vulnerabilities! Multiply that by every single golang binary anywhere in your system (seriously, I was getting CVE alerts for un-hardened stdlib cryptography in a 50loc file copying backup tool that could have been a shell script, and audio format conversion buffer underflow CVEs for Traefik, and many many more for months) and it adds up to a pain in the ass.
And heaven help you if you do actually have a vuln in third-party software that needs to be patched without an upstream fix (usually because "upstream doesn't make distributions on golang-$LATEST or $TRANSITIVE-$LATEST and they have a roadmap item to do that next millenium"). You can't install an updated transitive and fix it, you have to recompile and somehow distribute the whole thing. Doing that is never as simple as "go build" for big projects: the remaining 20% of build/toolchain needs that Go itself doesn't cover are inevitably fulfilled by the same pile of it-works-on-the-maintainer's-machine rickety Makefile bullshit we always had, but without even the sanity and conventions of autoconf and friends--and yeah, building others' Go projects in anger/in a hurry is enough of a pain in the ass that it makes me miss fucking autoconf.
On balance, I like Go. And there's a lot to hate about dynamic linking and package manager hell. But Go's approach is definitely not without its drawbacks in the CVE/security space.
Or, if you prefer more of a power tools feel, then HTMX and Raku in functional style (https://harcstack.org) maybe to your liking. Which I call the Crotch Rocket of the programming world.
> Hey, dipshit. You know what compiles in two seconds, deploys as a single binary, and doesn't shit itself when a transitive dependency gets yanked from npm at 3am?
Go, similar to Rust, has a horrible ecosystem, IMHO. I want to like it, but they already broke backwards compatibility with older systems (try to get the Go compiler running on a slightly older OS X, f.ex.), and for a compiler that's a no-go to me.
I regret to inform you, but the post was not about maintaining old systems, it was about simplicity in creating services using a language purpose-designed for creating services. The modern underlying os/docker image was kind of a baked-in assumption.
The post was about Go and uses the "The boring choice is the right choice." point at the end. But a compiler that's so quick to abandoned previously perfectly fine supported systems, and basically is bleeding edge, is anything but the "boring right choice". I personally prefer long term stability in the toolchains I use for my projects at least.
Bootstrapping a modern/ up-to-date C/C++ compiler works, getting an up-do-date Python onto the system works too. No such issues with either of those. But try the same with Go or Rust and you hit nasty roadblocks (in both cases the older OS was previously supported just fine.)
Isn't that the same thing that Homebrew does? It only has CI for the last couple of Mac OS X versions. Seems like this is more of an issue with Mac OS X architecture than Go.
Dunno about Homebrew (haven't used that since many years for various reasons), but Apple is certainly pushing things in a way that makes 3rd party developers quickly abandon old systems too. That's true. At least lots of 3rd party developers are very quick to give up if their new Xcode will not cooperate.
With agentic coding you can just use Rust. AI agents are really good at Rust and the good error message the compiler and or borrow checker gives makes it easy for the AI agent to adjust its code and fix it.
For non agentic coding Go has terrible error handling. It does not have exceptions or monadic error handling. Some call that a feature but many avoid Go or that specific reason. This will not change because that debate has been settled so if you can live with if err != nil after each function call (almost) then you are fine.
Things that is beautiful with Go are:
* Its simplicity
* Superb cross compilation support and excellent support for many different OS/arch combos. Not sure if anything comes close to this level of easy way of compiling to Many targets and target support.
Exactly, I don’t know why we are even arguing over this anymore. People basically don’t write code today so why not use the best languages?
Go is handicapped in significant ways which were specifically designed so that it was easier for humans to write. People pushing for Go code in this age either don’t understand the future or the past
After a few years of Kotlin, I recently ran into what I consider to be some shortcomings here as well, with respect to returning errors. As we know, Kotlin does not have checked exceptions. Ok, but ...
The KEEP for Result<T> goes into details, but basically there are a few ways of handling return values + errors:
1) throw exception if something goes wrong
2) return null if something goes wrong (stdlib XXXorNull)
3) Use Result<T> in some cases, its error type is not paramerized and catches CancellationException
4) Use Arrow if Result<T> doesn't meet your needs
5) Return Sealed Classes / Interfaces with all the possibilities.
Right now, I am going the Sealed Class interface route, but its such a verbose pain in the ass, so I only use it at certain levels of abstraction (like from a Repository, or library API, etc).
The code I needed to write was calling into okio, and it was not straightforward to figure out what kinds of exceptions would be thrown by the JVM layer underneath (docs just say IOException, but sublasses can be thrown).
Its sad to see they still haven't figured this out. Rich Errors was mentioned a year ago but its not even in preview yet. Its also not clear how it will work with Java interop.
I personally avoid Go because if the if err != nil
But I love the fact they have no exceptions. Rust handles this very well but it's way more complicated in general. I feel like we miss a middle ground.
Maybe when Gleam becomes really popular, it could be the one?
I still can understand the attraction with having the same language and codebase if you need a deeper level of interactivity on the frontend. That's where Node shines.
The big idea with LLMs is consistent references in the training corpus produced cheddar output by the language model during inference.
Go is an amazing language for language models because it's actually quite boring predictable while packing a lot of powerful distractions with a world class tool chain supported by Google and strong std library as well.
As a programmer I actually hated writing Go... and wanted to write Rust; but using coding agents makes me appreciate writing Go more.
I can get consistent results out while having concurrency cross compilation and predictability.
If you like a sort of weakly-typed version of Python or PHP, use Go. As the article points out it can be good for web forms. Not all development is web forms.
depending on the definition of static and dynamic, go could arguably be described as static (personally, i prefer declarative, as static is generally defined as something that i understand to be true for C but not for go), but that still does not make it weak.
I'm saying Go has a weak type system. This was apparently an intentional choice by the authors. Go doesn't even have sum types, which are an extremely basic, foundational feature of type systems. Even C had an equivalent in its union types.
"Weak type system" means that there are many restrictions on what constitutes a valid program that can't be expressed in Go, that can be expressed in languages with stronger type systems, like Rust, Haskell, or even Java and C#.
ok, fair enough, what actually interests me is why in your opinion it is weak.
in my understanding, weak typing comes from values being interpreted differently or are silently converted based on how they are used. php taking strings with numeric values when used in a math operation is a form of weak typing.
if that is not what you mean then you are using a different definition for weak typing. that's ok, i don't want to argue about what is the right definition, but i am interested in the definition you are using.
I was just using "weak" in the ordinary English sense, as in "not powerful". But you're right, that's confusing because of existing terminology.
I should have said that Go has a less expressive type system, i.e. the expressivity of its type system is weak.
It has no sum types, no algebraic data types, no real pattern matching, and limited generic constraints. And of course if we compare it to much more expressive type systems, which is a bit unfair because none of them are mainstream, it has no higher-kinded types, no dependent types, and no refinement types.
As for "true" weak typing, the idiomatic use of `any` (`interface{}`) in Go comes pretty close. While languages with more expressive type systems will often have some way to express that general concept, it's typically more constrained, more friendly to static checks, and less unsafe, because of the some of the other properties I mentioned, such as pattern matching where the compiler will warn you if you're not properly checking a value before unsafely accessing it.
The reason for my comment about Python and PHP is because to me, programming in Go feels little different in programming in those languages along with one of the bolted-on static checkers they have - you don't get anywhere close to the same level of static guarantee of correctness of programs. It's difficult to implement Yaron Minsky's adage "make invalid states unrepresentable" in Go's type system, and that's unfortunate.
Strange position to take. Python and Go are both duck typed but given Go has compile time type checking it is providing another layer of safety. Could you add more detail supporting your stance?
I find “locality of scope” to be extremely significant fact about a variable, and so I like seeing signs of it at one glance. The . as well for everything tied in a class and the go convention of shorter names for function locals also support this awarenes.
Maybe I develop games. Maybe I develop IoT devices. I might even be working in a high-stakes environment where formal verification is needed, who knows.
Whatever the case may be, we all have our reasons for choosing certain technologies. Not everyone is building run-of-the-mill 'backends' after all.
So please, let's stuff that neckbeardy arrogance away. It serves no purpose and distracts from the discussion.
> and doesn't shit itself when a transitive dependency gets yanked from npm
For non-trivial golang apps you're still gonna find npm in the mix. I recently packaged forgejo, yopass, and a few others, and if you don't have `npm` on the build machine, the resulting daemon won't serve the front end.
I know this is nitpicky, but whenever I see Go code I see those capitalized function or variable names and know: “aha, these were imported from another file; or will be exported later” and I think to my self: “why? oh why is that relevant information for my at this point in the code?” and I just think about what kind of a weird ill thought out design decision that was, just to save authors from writing an “export” keyword, and further judge the rest of the language predicting it must have more weird design decisions in it.
I've worked on large Go codebases on large teams at large orgs with large dreams. That garbage us straight up unmaintainable in the large and nobody can convince me otherwise. I have deep knowledge of the language, platform, used it for over a decade. It sucks.
My experience is directly counter. I've worked in half a dozen very large orgs and moving from interpreted languages to Go in each one made the system easier to reason about, more maintainable, and easier to onboard new team members. Go strikes a balance between all the competing priorities that has, in my personal experience, improved the engineering velocity across half a dozen companies comprised of hundreds of developers each. For two organizations, they made very coupled and hard to reason about code bases, but those were vastly easier to reason about than had they been written in, say, python or ruby.
I think this entirely depends on what kind of Java is being written. If you are Java developer on say Spring stack, moving to Go never ends up well in my experience. On several projects I worked on there was a push (strong word, more like investigation/POC) to start writing some services etc in Go and there was almost universal rejection at the end. So I think there is more to just language selection/preference but more like an entire ecosystem depending on what you are hacking
I would like someone to explain me Go. Really, I will use strong words but that's really what I feel.
The syntax changes a lot from the C one, and I can't see any reason for it. To me, it looks unstructured, with the lack of colons for example. It ignores memory safety, it feels like it ignored all of the typing system research since C, no discriminated union, and structures and types in general are heavy to write. It encourages bad patterns, errors out on mundane things like an unused variable, forces you to handle errors with a lot of code while not catching much more than C in terms of bug-prone practices. The package/module system is a nightmare for contributing to open source projects. Modifying a dependency to find a bug is very hard, even swapping a dependency (version) is annoying.
And what do you get from all of this compared to C? A garbage collector, tuples, and goroutines. No metaprogramming (aside from generics, and that was a whole story), interop with C is limited. To me, it looks like it does not focus on the algorithms, but on the code implementation, which is imo what leads us into poor programming and missing critical logic flaws, because the logic is buried. I may have forgotten other gripes I got while working with Go, but honestly, if I wanted all of that, I would pick D, at least it interops well with C and has metaprogramming (and has been made earlier, which excuses a little the lack of certain things).
But really, I am open to someone explaining me how they enjoy Go. Because I feel like I should be wrong as I see most people (which, for some of them, I know are clever) praise Go.
Edit: I added modal expressions to make it clear that it is my opinion.
I like it because I have more control over the size and layout of my memory structures than many GC languages, and the goroutine/channel data flow design model lets me use all the cores pretty evenly without having to worry about mutexes or subtleties. Pretty easy to get into the 100k request per second performance regime without special tweaking. I tend to either write long lived servers where the performance per container directly affects the cost, or analytics sort of Calais where I want my laptop to use 1/2 or 3/4 of its cores and get a faster answer from scraping 10M whatevers.
> if err != nil is the feature, not the bug. It forces you to look at every place something can go wrong and decide what to do about it.
No it really doesn't. It litters your code with if statements that are all just about the same, except that one that needs to be different, and you go blind looking at them all and can't spot the difference. And these days people probably just type "tab" and their LLM assistant fills out the entire block incorrectly in one keypress copying the pattern from everything else.
But LLMs didn't create that problem. Having to type something never meant you had to think about it, or thousands of "sudo shutdown -r now" commands would never have been run on production databases, because typing "sudo" would have magically made someone think about it, rather than just being keyboard memory.
And the problem of reviewing the code and spotting the one error handling block that should be different from all the others is always going to be there for human reviewers.
Rust converts the common case boilerplate down into one character: ? which lets you focus on any exceptional error handling rather than a wall of if statements that almost all look alike. And the compiler can see that you're ignoring a Result from a function call and force you to explicitly do something about. Plus you can then use a monad without knowing a single thing about monoids, endofuctors or category theory, and impress your friends.
> No it really doesn't. It litters your code with if statements that are all just about the same
Wrong thing to be bothered with. This allows static analysis of every possible path the code can take. Try to do that with throw/catch. There is a reason many industry guidelines like misra, jsf, etc just outright ban hidden paths. They have been literally catastrophic. There is a reason why many modern languages like go, rust, etc, want errors to be explicitly handled.
Go documentation specifically reasons why they treat errors-as-values and why is it like that - https://go.dev/doc/faq#exceptions
> Rust converts the common case boilerplate down into one character: ? which lets you focus on any exceptional error handling rather than a wall of if statements that almost all look alike
Again, wrong way to look at errors. Errors are not a problem for a compiler to resolve. Errors are part of your business logic. They need to be explicit and handled. The syntax of one language vs another is not the point of error handling. Errors are not a distraction. It is your job to review each path the code can take.
Code may be written once, but code is always reviewed or audited or referenced more than once. No one is impressed if you write some meta magic one-liner. I would rather be more impressed if I can read your code and form a mental image on how many paths your code takes to achieve an outcome in a single sitting, instead of pinging you for a quick call to explain yourself.
> you go blind looking at them all and can't spot the difference
> And these days people probably just type "tab" and their LLM assistant fills out the entire block incorrectly in one keypress copying the pattern from everything else
Now, if someone is 'going blind' looking at error checks, its because their function is doing too much and has too many failure points. Perhaps one may need to focus on honing their software engineering skills on how to structure their code, instead of hitting that 'tab' more so often.
Go has plenty of hidden code paths in defer, panic and recover. You can minimise the use of those things, just like in other languages you can minimise use of try-catch in favour of a Result sum type, which is already better than Go's "multiple return that you have to pretend is a sum".
Which is what should be discussed about go, rather than if-err clauses. That is hardly an issue in go. But Go has these little quirks which, if not careful, can become a problem. But instead most go critiques discuss only the error handling syntax. Look at the other comments.
The article is on point on the strengths of go, and in many cases, those other languages you say, you will find it really hard to do the same thing with the ease with which you can do in go.
What the article glosses over, is the footguns with working with the language. No compile time nil checks, implicit interfaces, lack of sum types, like you said, which more so often leads to bloated structs, member visibility which is just private and public. You can find more.
But these problems were not the main focus of go. Go was build because google devs wanted a simpler way to manage and deploy their software across thousands of services, not having to wait forever for their code to compile, not worrying about writing cpp code in the BEST possible industry standard, ability to do parallel processing of hundreds or thousands of workloads without choking your cpu, managing dependency hell. These were actual roadblocks. And google was not the only one facing these problems. Go solved all of it. Which language was solving this before go?
Go was developed because two UNIX guys, and one Oberon guy, that didn't like C++ decided to create their own thing, and their manager decided to support them as their 20% project.
Nothing else.
The narrative of Google wanting Go never happened, it gets sold as such by many Googlers.
Even Kubernetes was originally written in Java, before some Go folks joined in and drove the rewrite into Go, which besides the Android build system are the only two key projects using Go.
https://go.dev/doc/faq#creating_a_new_language
Yes, and?
Most of that was written by the Go team, not Google management, and even has the famous Rob Pike take on it.
The You Tube download rewrite from Python to Go, wasn't a management decision, rather yet another case of Go team taking language marketing into their own hands.
You will find posts from me on golang-nuts pre-1.0 days, as I was hopeful it would turn out differently back then, so I don't need education on how Go came to be.
> Most of that was written by the Go team, not Google management, and even has the famous Rob Pike take on it.
I am unable to understand your point or you seem to be making arguments that have nothing to do with the article nor with my earlier replies. I wonder why. Are you expecting sundar pichai to announce golang to his shareholders in his quarterly call? Which language does that?
This article and my replies are technical in nature. You need to have a technical response to a technical problem. You cant get emotional everytime. Which language allows me fast builds, lightweight and high performance concurrency, strong stdlib and tooling, easy cross compilation with static binaries, simple syntax, decent performance and by that i mean not worrying too much on memory management, easy onboarding and large scale maintainability? I am willing to try that tool.
Because your narrative is wrong, it wasn't a Google project, it was a group of Google employees that rebeled and were lucky to have their line manager support, given being UNIX idols.
Google upper management was, and is, perfectly fine with Java, Kotlin, C++ and Rust, for the most purposes.
I won't suggest any "technical" answer, because clearly you only want Go.
> I won't suggest any "technical" answer, because clearly you only want Go
You can’t provide me any technical answer.
> No one is impressed if you write some meta magic one-liner.
I am very impressed if they can express the entire algorithm succinctly with a one liner. That's the kind of abstraction every piece of code should strive for and be shamed to the ground if they haven't.
> I would rather be more impressed if I can read your code and form a mental image on how many paths your code takes to achieve an outcome in a single sitting, instead of pinging you for a quick call to explain yourself.
Does this mean you're impressed by the ability to read go's if spaghetti?
> if they can express the entire algorithm succinctly with a one liner > That's the kind of abstraction every piece of code should strive for and be shamed to the ground if they haven
This obsession with one liners is inversely proportional to professional experience.
> This obsession with one liners is inversely proportional to professional experience.
Yes, I see this sentiment on every billboard. It is a sign of mediocrity.
I agree with all the motives you describe, yet come to the exact opposite conclusion.
The overwhelmingly common case is for an error in a nested call to be bubbled up to be handled by a parent call. If you make this common case look similar to the distinct case of actually handling an error, you just obscure where the real error handling happens.
Writing good error handling is Go is really hindered by two other issues mixing together: 1. There’s no Result type, so while it tries to treat errors as values, it’s missing out on a lot of the benefit of that idea. 2. Multiple function return values are implemented as a special case, and aren’t themselves a value
Most languages that support multiple return values, do it via some notion of tuples: returning a single aggregate value that you can pass around as a whole, but that also have some nice syntax for destructuring them into local variables. Go implements as a syntactic special case.
You can’t assign the multiple return values of a function call into a single variable. You’re forced to take all the parts, and pack them into a struct yourself. This means that you can’t factor your result-handling logic into testable functions, without needing to do this dance at every call site.
> Wrong thing to be bothered with. This allows static analysis of every possible path the code can take. Try to do that with throw/catch. There is a reason many industry guidelines like misra, jsf, etc just outright ban hidden paths. They have been literally catastrophic. There is a reason why many modern languages like go, rust, etc, want errors to be explicitly handled.
I mean I'm explicitly supporting how rust does it over go, so I don't understand how this is any kind of rebuttal. In your last sentence you admit that rust enforces that errors are explicitly handled, and I think it does it much better than go does, and does it without littering the codebase with boilerplate if statements. The fact that you're launching into an explanation of the insufficiencies of throw/catch exception handling when that has absolutely nothing to do with what I was arguing... Well, I don't feel the need to argue with you any further. Rust also has errors-as-values and doesn't throw (at least not the common case, just like go), so the go FAQ on exceptions isn't relevant to the discussion.
And I'm just going to end the argument with that, since you seem to be a waste of time to deal with.
> No it really doesn't. It litters your code with if statements that are all just about the same, except that one that needs to be different, and you go blind looking at them all and can't spot the difference.
If most of your error handling is just bubbling the error up, you are doing something wrong.
is an antipattern. Not because it's verbose, but because you are supposed to handle the error, not pass it right through, in most cases.
There's a lot of merit in this. I call Go the Honda Odyssey Minivan of the programming world. It doesn't do anything exceptionally well but it does lots really well and in a way that's simple and reliable. Especially for the backend serving react front end niche.
But it's also a pig to write and comes with a lot of foot guns. Especially the Null handling. Somehow they made it worse than every other language.
Nilaway linter FTW here.
Why the Null handling and in Go is worse than others?
It sets primitives to 0, "", false etc. Which is almost always but not always fine. And if they're complex objects you still get NPEs
To get true nullable fields you need to use pointers. That's a whole topic in itself but they're awkward.
It's much worse than true nullable objects that your compiler can check for NPEs. It throws fewer NPEs but at the expense of data integrity where you don't know if your 0 is actually a 0 from the user or a null.
What if Go went all the way? Referencing a zero pointer (nil) gives you the zero value of the pointed to type. If you try to access a zero map, it tries to deference the zero pointer to the underlying buffer. The zero pointer gives you the zero slice with zero length. The presence check fails without crashing and you get some pretension of reasonable behaviour.
So what happens when you write through the nil pointer?
Nil of course (nothing happens). Not saying it’s a good idea, it would just be consistent.
Well, what happens when you write through an invalid pointer? The runtime can swallow the write or crash the program or allocate new storage and replace the pointer. Depends on the wanted semantics.
Really bad idea.
Silently returning some (the wrong) value is always worse than catching the error right then and their and panicking. A panic is a noticeable symptom of something just having go wrong that is easy to chase after and debug. A silently returned wrong value just causes data corruption down the line, at great pain. It is very annoying to debug, in particular if people start to some times rely on this behaviour as an intentional part of their data flow.
I agree. I prefer Optionals over Nulls in any case. This was just a thought of what if Go went all the way instead of just halfway. In my ideal language, you would have explicit assignments and compiler checked (runtime checked if absolutely necessary) pointer validity (with allowed length) at construction. As far as I know, the only time you need to make a pointer and say "trust me" is when doing MMIO so that can given its own mechanism. If it exists in the program state, it's valid.
I think there's a large subset of programmers now who consider null checking (or even the existence of null) to be bad, and prefer something else like exceptions or option types. I don't get it personally.
The existence of null is not the problem, it’s when null populates every single non-primitive type, making every access into a logic bomb unless explicitly checked. When null is a distinct type, there’s no problem at all.
Another reason why it's worse is how it breaks logical laws thanks to the nil interface bug: https://go.dev/doc/faq#nil_error
Well, they'll call it a design decision, not a bug. I guess I'm being charitable.
Some weird nils don't compare equal to other nils. It's surprisingly easy to construct the weird kind by accident, because the conversion is automatic and invisible.
I like go, but a lot of little things stop me from loving it.
Like, enums. I get a lot out of the box when I use an enum in Java or Kotlin. Converting to/from a String is trivial. Type safety ... exists.
I can do that in Go, but I have to hack it in, for every single enum type I want to represent. Enums are not a thing in the language, which means its easier to keep the language in your brain all at once, but at the expense of making it harder to keep the software I'm writing in my head. Is this "enum" the same as that "enum"? I have to go read the code to figure it out.
But Go is excellent at a lot of things. Compile times, static binaries, resources compiled right into that binary, execution speed ... there is a lot to love.
I really wish they had added Enums instead of the stupid generics.
I would already be happy with enumerations Pascal style from 1976, without the const/iota dance.
The convention of explicit error handling after every method call is absolutely bonkers to me. I would never use it for anything serious.
I recently vibe coded a large app in Go, it’s so mind-numbing to read. Like half the code base is error handling.
The anti-exception mind virus of the 2010s did a lot of damage. Go designers finally caved and added panic & recover, but jeez, the damage was done. The whole ecosystem has exception derangement syndrome.
Go had panic/recover right from the get go. Nobody “caved.” And indeed, you are free to use panic/recover for error handling in your code. That was always allowed.
That said, the key insight of the exception craze is that error returns are a normal part of a functions behaviour and as such should not use extraordinary control flow to take place.
Exceptions (panics) are used for things that should never happen or are indicative of a programmer error, like nil dereferences or out-of-bounds array accesses. That is, things where the programmer is not expected to provide reasonable behaviour on the API level if it happens (but perhaps there is a whole-program fault handler to shut down cleanly).
Opening a file that does not exist? Database record not found? Invalid credentials? These should be anticipated by the programmer and can occur at any time. Not exceptions, normal return values. Go requests that you think about these possibilities instead of pretending they can be ignored.
You're right about recover being in 1.0 of golang.
I definitely fully understand the trade-offs of returning result/error unions vs handling thrown exceptions. Exception handling is clearly superior to me. That said, the typical performance complaints made against the most common implementations of exceptions are valid.
Since AI coding, I've switched 90% of my code to Go. It's really great for most things. Lacks a development community large enough to have a really solid UI framework, but the existing frameworks are "good enough". I used AI to make an AI agent that works on Windows, Linux, Mac, iOS, and Android, with CLI, GUI, and web serving. LOC: 5575. Binary size: 35MB.
Also: why can't we vouch for flagged stories now? This post is actually good, and funny, and the conversations are worth having.
I love Go. But I prefer .NET for web development that also compiles to a binary and has a great ecosystem of libraries and packages. Go is great if standard library works (and it can for many cases) but when you need to start looking into non standard libraries, Go can hit limitations.
For example, to build a full production web application with database in Go, there is no great out of the box migration tool. There are some good 3rd party libraries of course but compared to something like EFCore in .NET, they don't come as close.
For me, it is now .NET and then Go. Of course, I use Go when just doing a lot of non web stuff as well.
Comparing Go and .NET is like comparing a Honda with an aircraft carrier with wheels.
Eh, at least for people consuming .NET apps compiled with NativeAOT, the experience is similar. Applications can be compiled as a single file with no dependancies. A hello world in .NET is half the size of one in Go:
https://github.com/MichalStrehovsky/sizegame
> that also compiles to a binary
"compiles to a binary" is not a useful criterion. The criterion Go is winning on is "compiles to a single, completely self-contained binary," meaning it does not depend on libc or any external runtime. You can't say that about .NET. You can't say that about damn near any other programming language. It's extremely rare. The fact that .NET uses a binary packaging format is, like... well ok, so what?
.Net can compile to a self-contained binary: https://learn.microsoft.com/en-us/dotnet/core/deploying/nati...
That isn't truly self-contained. It still relies on libc.
You can make it statically linked using musl. (This is underdocumented because Microsoft thinks it's usually a bad idea: https://github.com/dotnet/sdk/issues/37643#issuecomment-1873...)
Oh that's good to know. I happen to think Microsoft is chasing a bad philosophy with that declaration, and there is no more danger in statically linking ssl if you're continuously rebuilding and deploying your statically linked scratch image, but then again, the Microsoft approach to a lot of things isn't what I want in my datacenter. To each their own, I guess. I happen to love how self-contained Go programs can be and do not consider that a liability but a strength.
Nah. Go also relies on libc if you do anything non-trivial, like look up a the IP address of localhost.
Lol well I don't know what the trigger is for pulling in libc there, because I've built massive scale services that did a lot of nontrivial stuff and then the deployment was a single-binary docker container that did not have libc. The only thing needed to be put in the container was a directory full of root certs so it could do TLS.
(full disclosure, I don't think I ever had my service look up the address of localhost)
edit: seems like you probably have CGO_ENABLED=1, which is now the default and will cause simple networking things to use libc. Set CGO_ENABLED=0 and you won't have libc.
I suppose you are missing the point. Go had support for self contained binaries since its introduction in ~2009. This was not the case with dotnet, if i am not wrong.
But nevertheless , if any language provides something like that, win-win for entire ecosystem.
Agreed. The fact that .NET has a way to do true static linking is great and I didn't know it existed. The fact that they apparently think it's a weird and undesirable thing is less great and would make me worried that they're going to undermine the ability at some point. Golang has had this ability since way back and they think it's a strength.
I can't see any reason this list why I should use Go over C# / .NET.
.NET has almost all these upsides, but with a concurrency model (async/await) that is (now) more transferable to other languages.
Agreed-- c# has a lot of these advantages and is a lot easier to write (yes, I know this will depend). Plus it has a much larger ecosystem
The only thing I can think of: I dont think c# can compile as easy to a single executable binary, like Go (or even rust)?
It's had support for single exe compilation for a while now, although the file sizes can get large without being more careful about dependencies.
.Net SRE here, you can do self contained executable but there is some foot guns there and if you are doing containers already, I'd just skip it.
I can see reasons why people don't want to use .NET if Go is available. .NET has its merits but it's bloated, compilation is slow, and I find it's tooling to be really annoying.
For me go is just above c# and both of those are not super high on my list.
I've never understood what is meant by "bloated", would you mind explaining, so perhaps I could better understand?
If it's "Large standard library", I think that's a selling point. Having anything you need available ( although these days, via optional microsoft.* packages ) helps keep projects consistent between different places.
If it means "Different ways to do the same thing", I can understand that criticism better, and some of that comes with 20+ years of legacy, where the wrong thing was done, and now there's a better way, but a ruthless adherence to backward compatibility means that the old way isn't dropped.
The .net sdk is like 1gb, go is like 60mb or something. It's annoying when that matters.
More so, nothing happens quickly with it's tooling, and the tooling isn't friendly. All of a sudden I can't build my project in vsstudio(also bloated but admittedly optional but may be required depending on where you work). Clean build also fails. So I have to go to the command line and type in dotnet restore, dotnet build, magic it works now. ???
Okay time to install a package because msft has its own packages for things like aspnet but now I want to serialize json. Cool newton soft sounds good. Ah now I need a package manager, I'll install nuget. Oops I need to tweak this weird xml file with lots of options.
I press compile I wait 3 minutes to realize there is an error. Okay it builds and looks good in debug mide now I want to send my application to a friend who doesn't have .net runtime...
With go it's more like. Okay I automatically have web access from the std lib. I want a framework oh I already have a package manager. Edit my toml. One command done. Time to compile, poof done in 2 seconds. I send the binary to my friend and they run it.
I understand some of these are 1 time cost things, but I am requesting you to read between the lines as these aren't examples I am trying to quibble over. The point is the friction that go has focused on removing by being quick and small. It has less legacy cruft but I tried to ignore as much of that as I could.
Also keep in mind j am not a big go fan. It's not my favorite language but it is way easier to deal with on a regular basis for me
That's quite a rant, but it seems like it's from experience with the old times. For the last decade or so it's been a completely different story.
System.Text.Json is part of the core standard library, the only reason to use Newtonsoft is developing on legacy applications.
Visual Studio isn't necessary or even much recommended any more. VSCode or any LSP editor works fine.
You don't need to install nuget, any more than you need to install cargo in rust, it's part of the SDK. You can also just edit the csproj in the same way you could edit your TOML file. It's XML, and you just add <PackageReference="" />.
You can compile --self-contained to be able to run it somewhere without the runtime installed.
.NET has a long history, but almost none of these are actual points of friction in the current year.
I disagree as someone who was using dotnet within the last 2 years.
Also we are comparing go to .net not to rust. Go is much easier and faster tooling wise. It doesn't require an IL and doesn't have 2 decades of enterprise cruft with vendor lock in strategies baked into it.
I asked to read between the lines.
The experience behind each of these things really stinks compared to go. If you haven't tried it, I recommend it just to feel the ergonomics. I don't care for go as a language. The funny thing is, I find the tooling so much easier, better, faster than .net that even though I like c# more than go as a language, I would prefer to start a new project in go instead of say c# instead. It's that annoying for me. I've met several other people who are language ambivalent who feel the same way.
I could write a huge blog post about it with facts. I'd rather not though. People do what they like and I am not here to change what someone likes.
.NET is too slow to use in my pc
I only have one reason, devops ecosystem where Go is expected nowadays, like plugins for something.
Otherwise Java, .NET, Typescript (with possible C++ addons).
Go is fine for simple applications especially backend ones that connect to the internet. I do prefer it to node/js/ts/etc.
I do think a lot of projects would be better served having been written in go instead of java, or whatever else.
I don't think it's a panacea for anything. It's pretty easy to shoot yourself in the foot with. The easy stuff is easy the hard stuff is really hard.
I like rust a little more, and I don't rewrite things with it. I choose it first. That's my preference but go ahead and gopher on.
Every time I see those kind of posts, it seems reasonable on the surface. But then you remember than a typical web app might want to do something beyond just serving an HTML template.
Like, what if you want some styling or javascript along with your HTML? Either write everything by hand like it's 2003 again, or start inventing a complex pipeline from scratch in Go. Countless of people have spent thousands of man-hours on solving this problem, but you use Go, so now you do this yourself from scratch.
Or maybe your app needs some relatively complex database access? Writing SQL-spaghetti only gets you so far until you need to do conditional joins and unions and parameterized subqueries etc. God forbid you want migrations too. Go has no answer for this and you either have to hand roll all those things as well, or spend tons of time comparing countless libraries that do some of things you want, but not quite everything, or don't fit into your pipeline for whatever reason.
I'd take an opinionated framework with fat dependency stack that has way more features than I'd ever need over trying to reinvent migrations or CSS preprocessing from scratch in Go, thank you very much.
I often think of go as a "better" python. As in, easy to learn and easy to use. But also performant and the module system and package manager seem to be a little neater. (sorry for flamebait)
But I wonder how well it can cover similar use cases? Go is great for devops and web backends. But what about AI and data science?
If Go interfaced with C as well as Python, I’d use it a lot more.
But I’m using the slower language because it still integrates with more things
For example, one reason AI is all in Python is because CUDA is basically part of the C ecosystem (ie build system)
Have you tried purego?
You can just embed the C library into the binary of the Go app call it directly in Go. Most of the time I have found calling C from Go faster or on par with calling C from Python.
https://github.com/ebitengine/purego
Go interfaces with C better than Python does...
I used to do lots of data engineering in Python, then started doing all kinds of engineering in primarily Go.
The Go ecosystem for data is very limited. There are no widely supported dataframe libraries (like the og pandas and the newer polars written in rust and also available as a crate). Very few data science libs, a few decent gen AI libraries, but not as popular as their Python cousins.
Most of the work I do now is streaming data and very small batches. For that Go is amazing. I don't need dataframes to transform a json, combine it with a bunch of other data and write to a database. I just need to write that logic and make it go fast. Very easy in Go.
Yeah, it breaks when the author decides to move from Github into Gitlab to protest against Microsoft.
Time to update all code references to Gitlab across the globe, in every single Go project.
Or spend the time configuring redirects between URL mappings, across everything that depends on it.
Not to mention that except for lacking garbage collection, even Turbo Pascal 7 for MS-DOS was more modern in language features, with faster compile times, on a 20 MHz powered computer.
> Yeah, it breaks when the author decides to move from Github into Gitlab to protest against Microsoft.
The import path is disconnected from where the build system looks for the files. Of course, the default is to use the import path as an URL, right, but nothing forces you to do that. And if you do, *you* lock yourself to the hosting you use. But that can be hardly blamed on Go.
Documentation here: https://pkg.go.dev/cmd/go#hdr-Remote_import_paths
Full example: https://github.com/rsc/swtch/blob/master/app/rsc-io/main.go
tl;dr: Serve this at your domain, update the repo-root when changing host platform for your code: <meta name="go-import" content="import-prefix vcs repo-root">. No one else has to do anything.
I am well aware, as you might find out in my comment history all the way back to Go pre 1.0.
Additional work that is superfluous in other mature programming ecosystems, that don't have the dumb idea to use SCM urls as import paths.
Why is this flagged? Because of the swearing? Cause there have been previous posts with swearing in them? Or do people in general hate Go so much?
I know the OP said dont use external libraries, but I love bubble tea (And their related libs), they are a great reason to use Go for TUI
that said, I only use Go for hobby projects, I dont know how good it feels if you have to use it for work 40 hours a week
It's likely being flagged because people believe it's more AI-written than not
Let me copy paste my other comment:
"This is getting really fucking irritating. Every 3rd comment on every HN post is "This is LLM", which has become a proxy for "I dont like it so it must be llm"
Yeah, I suppose I am in the minority here, but I still judge a posts on their merits.
LLM witch hunts are tiresome.
(Perhaps the worst part too is that an accusation can be leveled and there's no way to counter. If it's a vacuous fluff piece, say so—irregardless of who/how it was written. And there we are judging a piece on its merits…)
Yeah I didn't flag it myself. I skimmed it and I didn't get an immediate AI vibe, like someone else said it seemed to be trying to use the motherf*in website style
Unlike witches, LLM content exists. And worse, it has proliferated to a point where you are more likely to spot LLM content in the wild then human written content.
Witch hunts are bad because they target innocent people and burn them at the stake. When the whole internet has been filled with LLM content, it is not unreasonable, expected even, that you start accusing everything of being an LLM, because, most likely, it is.
This one has major signs of LLM speak, specifically Claude.
Normal humans who rant like, actually rant. This is AI generated rant.
Yeah it's taking itself a bit more seriously than an actual human would.
I am so over the presumptive blog post tone personally.
Maybe because the style is just worn-out ragebait trolling now.
I love go, but I find it did little to make concurrency management easier to reason about. Race conditions are easy to write. Go routines have all the same concurrency problems of threads.
In the parallel HTTP fetcher, the error is discarded. This will likely result in a panic when the response is nil. Also, what if it a server locks up? Or the underlying socket never connects and never times out?
I know it’s a toy example, but one must consider all these things in a real system. Go does have good pathways for these concerns, but it’s also easy to do it wrong. I still have to manually reason about access to variables/struct fields from multiple go routines.
I find I have to design servers from a data flow view; when my performance demands let me do everything via channels to worker goroutine pools (including sending a response Channel back to the request handler) I don’t have to do any locking at all. I find when I have to add in mutexes it means I haven’t thought enough about the problem (or I am fixing some hot spot from the profiler, rarely).
Its absolutely laughable that Gophers act like their language solves concurrency when its actually worse than Java because it has data races. Go added nothing and moved backwards.
The language that actually solves concurrency is Rust, but they will never acknowledge that.
Now that LLMs can breeze through the Rust boilerplate there's no reason to ever write Go again.
It's one of the dullest, most mediocre languages out there and despite a nice toolchain and the fact it's undoubtedly a "safe" choice, I just have zero interest.
Someone has to review the LLM generated code. If it's not the developer, it's the SRE at 2 am. For someone without significant experience, it might be harder to review Rust than Go.
Not sure it's harder. Rust type system makes it much harder to shoot yourself in the foot with nils and errors, at least. Biggest issue is probably the number of 3rd party dependencies every non-trivial Rust app ends up needing.
I guess other languages that follow a stricter/larger compile time regimen e.g. Haskell, OCaml, Idris, ATS, F* would be suitable as well for this kind of approach. Like Go, OCaml seems to offer low compilation times.
I bet if Rust was more mature back then, the Python to Go rewrite from Docker, and the Java to Go rewrite from Kubernetes would both have been into Rust instead, and that would be it.
I'll grit my teeth and tolerate "if err != nil" if I have to, but null pointers are where I get off the train.
I can write go but I don't prefer it. It's ... okay.
Things I dislike:
- if err != nil. Just give me some syntactic sugar instead of letting me write the same thing a bajillion time.
- no way to bind a struct to an interface. I'd like my IDE to tell me when I accidentally stopped implementing an interface
- some stdlib parts are too bare bones. Unpacking an archive requires me to handle all files, directories, links, etc. myself. There is no move command that can move a file or directory across fs boundaries. The little things.
> I'd like my IDE to tell me when I accidentally stopped implementing an interface
I don't know about others, but Goland's analyser is pretty powerful and can navigate from interface to implementation(s) and vice versa.
In my experience only when you implement it correctly. If I add a new method to the interface I can't navigate to the implementations anymore because they don't completely implement the interface.
That's surely the correct behaviour?
It is. But it's also an inconvenience. In a language like Java I can just say that a class implements an interface. If the interface changes the compiler and the IDE tell me about the missing methods. Go not so much.
Now your compiler will tell you you stopped implementing the interface. Pretty? No. But it works. And gopls will even offer to implement stubs for missing methods.
That is helpful but I think that is still a fail for Go. This could and should be a simple annotation.
This also works:
In my mind as I read this, I am reminded that it is the receiver type *MyStruct that implements MyInterface. YMMV.
This is a useful hack but just imagine someone coming from another language reading this. They'd have no idea what it's for. It looks a bit like the things C people do to themselves. And all just because someone didn't want a keyword or another form of annotation to signal that a struct should implement an interface.
These are the small things that annoy me about go.
> The boring choice is the right choice. It always was.
Right, absolutely correct, Java is a great choice, so why does this post keep going on about Go?
Where my brain went.
Lack of extensive standard library, for one. But yeah Java is pretty great. Spring boot/quarkus especially
EJB, Spring, Ant, Struts (I'm getting old - Like Hot Java Alpha 3 and Java applets old), maven, pom files, etc.
I used to love Java but the complexity merchants showed up and ruined the party. 1.5 was just coming out when I stopped doing Java dev. Kotlin might pull me back into the fold though for when I can't use Go.
The good stuff landed in Java 8, so you left the party too early. From Java 8 on it has changed direction completely (less OO + JavaBeans, more functional + ADTs). It feels like a completely different language from the Java 1.4 / 5 days.
https://en.wikipedia.org/wiki/Java_version_history#Java_SE_8...
You can write apps without that stuff? Sure you should have 1 dependency management system, but otherwise you can write clean JVM apps in many languages, without DI or much else.
jeberle made a completely factual reply to this, but people hate Java so much it's downvoted to dead.
That was a long and dark time in history. We're still in the shadow of it. But these days a much better world is available - a more powerful language, good libraries, and much, much simpler frameworks.
To be clear, all the annotation and Java EE stuff is still going if you want it!
My big issue with Go is, the language just isn't that great. Zero values instead of sum types, reflection instead of proper macros, a mediocre module system…
Java's warts are far worse than Go. Everything is nullable. There's no module system to speak of. It's so IDE-dependant.
I agree with the spirit of "use boring technology." So thank god Go is boring enough that I don't have to write Java anymore.
> Everything is nullable
Only reference types, the same as in golang (which also has nullable pointers, and its interfaces interact weirdly with null). Java is getting value types, which can be declared as non-nullable.
> There's no module system to speak of.
https://dev.java/learn/modules/
> It's so IDE-dependant.
Modern Java has been simplified that you can run a hello world program as follows:
Furthermore, any non-trivial project is better served with an IDE, regardless of language.
> Only reference types, the same as in golang
Everything's a reference in Java except primitives, and even primitives get object wrappers. In Java, String, Long, and Bool can all be null. Go isn't like that—only explicit pointer types and interfaces can be nil. In practice it really cuts down on NPEs.
> https://dev.java/learn/modules/
Ok, granted, but I have never seen these in actual use. Java's ecosystem is big enough that you can use Java for years and not even know it has modules. I find this profusion of features unboring for the same reason that C++ is unboring.
In Go, everything's already in modules. It's just simpler. And when they did add generics, it was backwards-compatible, so there was no Java 8/Java 11 thing.
> Furthermore, any non-trivial project is better served with an IDE
In any other language, I can get by fine with vim + LSP regardless of project size. Java has a uniquely bad LSP story.
> Everything's a reference in Java except primitives
Value types will have the ability not to be nullable, which is the same as golang.
Primitive wrappers are used to denote nullability, which golang also has in its sql package for example.
> I can get by fine with vim + LSP regardless of project size
Getting by is one thing, having strong introspection offered by sophisticated IDEs is another.
Well, Spring (Boot) is so hard to avoid running into at day jobs. And Spring is hell.
Go probably wouldn't exist if Java devs went easy on overengineering design patterns.
But overall Java, like Go, IS a boring language. And that's a compliment.
> Your "clever" coworker can't sneak a seventeen-layer abstraction into the codebase because the language won't let him.
Oh boy, the author has clearly not seen some of the Go codebases I’ve seen.
> `if err != nil` is the feature, not the bug. It forces you to look at every place something can go wrong and decide what to do about it
Haven't really used Go, but can't someone just `result, _ := foo()` and go on using `result`, not checking any errors?
The way Rust does it seems closer to forcing you to handle any errors in order to obtain the result (though it is still easy to just `.unwrap()` without properly thinking about it).
We do want to check for errors, we'd just prefer that it not be a repeated 3-line boilerplate pattern that ends up being >50% of all code. Rust does it with one character.
I agree that Rust's approach is better. I'm questioning the claim that Go "forces you" to handle errors, since to my understanding with Go someone can just eschew that 3-line boilerplate, silently ignoring the error, and still use the result (which is bad).
The point being that's how you've decided to deal with the error, by ignoring it.
Silently ignoring errors by leaving out some boilerplate doesn't really seem like an active/forced decision, or a selling point over the languages it disparages ("[...] hellscape doesn't make errors disappear, it just hides them"). Then that the correct path is the one of more resistance seems poor design, in my surface-level opinion.
It is a decision though, and one you have to consciously make for each and every occurrence. You can't just hope some catch block will eventually handle it...
I may be missing something, but I don't see how just forgetting to handle/propagate the error (and, say, causing data corruption by continuing with whatever empty/partial return value the function happens to give) would be a conscious decision.
Even when it is intentional, like writing some quick/dirty code and planning on handling errors properly later, I'd imagine it's difficult to grep for instances of unchecked errors in the way you can with `.unwrap()` - though tooling should help.
Personally, I don’t want to swallow errors I want to throw them. 90% of the time, I have a few small cross-cutting points (like an api controller) I want to catch and handle exceptions that get thrown from the rest of the code base.
After having way too strong opinions about this, and even if Go is my go to language, I've stopped caring for this kind of statements.
In the end, the language doesn't really matter and extremely rarely is the bottleneck.
Python, Java, Go, C#, PHP, Node, Ruby or Scala, for backend stuff, all these languages can do the work 99% of the time. They all have well developed ecosystems, and are sufficiently mainstream to easily find developers.
In the end, these languages are just variants of the same tool, and I find more interesting to just pick one and build something with it, rather than bickering endlessly about which one is better.
> if err != nil is the feature, not the bug. It forces you to look at every place something can go wrong and decide what to do about it.
It truly does not.
The single executable deployment hasn't been an advantage for a very long time. C# has Native AOT. Java/Kotlin/Scala have GraalVM native-image, plus Kotlin Native and Scala Native.
And of course there is Rust, which compared to Go has better performance, lower memory usage, smaller binaries, safe concurrency, safe resource handling, and a real type system with tagged unions.
The Go team built good tooling around a mediocre language. Now most other languages have caught up, and Gophers are left writing mind numbing `if err != nil` checks with no benefits left to show for it.
I read this, as my computer crashed while compiling a 1000 dependency CRUD rust app, for the fourth time today. Then I take a deep breath.
If the toolchain crashed while compiling, please file a ticket! That shouldn't happen ever and if it does it is indicative of a big problem that needs fixing.
Go is probably my second least favorite language, right behind C++
You need a lot of linters e.g. to make sure errors are being handled, and the lack of algebraic data types make expressing data difficult.
I do think it has merits, but I’ll take type safety over simplicity
Apropos of nothing: it's interesting that a page pushing Go so emphatically is built[0] using the Rust-based Zola rather than the Go-based Hugo.
[0]: https://git.sr.ht/~blainsmith/blog/tree/main/item/.build.yml
Not mentioned is that Gemini does a pretty good job of writing Go in my experience of using it to generate utility scripts, and a friend’s use of generating an internal website for using a corporate API.
I've typically leaned towards Python for my agentic programming, because the LLMs have been good at it and I'm familiar with it if I need to take a look. But I'm just finishing up an apt-cacher replacement and decided to use golang and the experience has been really great.
I'm using CC+Opus 4.7 max effort, and it's produced a working apt cacher from the first phase of development, so far there have only been a few things I've had to ask it to fix. This is over ~52KLOC (counted by "wc -l"), going on day 3 of it working on it. This includes: caching proxy, garbage collection, "http://HTTPS///" kludge (apt-cacher-ng semantics), MITM https proxy, admin website + metrics, deep validation of metadata and rejecting invalid updates, snapshots of upstream state and delayed metadata update until "hot packages" are available after metadata update...
10/10, would go again.
FYI: My agent loop is: "Work on next step, have codex review it, compact", and then a couple rounds at the end of a phase to review the code against the spec, and a couple rounds at the beginning of a phase to create the spec.
I start out writing most of my terminal applications and utilities in Python but when something hits a performance ceiling I convert it to Go. That's been a pretty good bar for when it's time to use Go for me and so far so good.
Go or Rust in 2026 is the standard go to and I never looked back on anything else.
I would not consider anything else without a good reason and especially never going for Javascript or TypeScript for anything server related.
The best feature of Gon is that it is boring.
You cose something and if you don't use some weird 3rd party packages (Go stdlib is quite complete) you can check that code again in 3 years and will still work.
Hmm, they misspelled Gleam.
In seriousness, Go is a good choice. Or at least it’s not a bad choice, I’d definitely pick go over many other languages. If go had a better type system, it would be damn near perfect.
Maybe some common complaints about Go are finally less of a problem in the current coding agent era - e.g. ecosystem weakness complaints and verbose error handling.
Though TypeScript's type system is maybe still more powerful - and therefore might have the edge for agents writing code? (Not to mention there's probably more TypeScript in the training data for LLMs, though perhaps there's _better quality_ Go - I'm not a Go dev though so I couldn't comment further on this.)
I got turned off of Go pretty fast by GOPATH shenanigans and polluting my home directory for no reason, since I don't think a programming language should really have any say on my filesystem.
Error handling also seems pretty dumb in comparison to Rust. Admittedly Rust is a much more complicated language, but I felt like I could just go learn more Rust instead of bothering with Go and have more fun.
Doesn't Rust/Cargo dump stuff into your home directory as well? https://doc.rust-lang.org/cargo/reference/environment-variab...
Like, if the gripe is that Go should be better about following XDG conventions, fair enough. But pretty much every language other than C, C++, and Node tends to use system paths for transitives/compilation artifacts/registries/downloads.
I have been using Go since 2014. I have services that just run without issue.
Having backwards compatibility with 1.0 just makes it easier to maintain software.
The big plus in the modern era is that the simplicity of the language lends to having agents write Go without much fuss. That and the standard library being batteries include lets you direct the agent to use little or no third party dependencies.
I should write one of these for Haskell. Huffing abstractions is great for boring, line-of-business applications.
Goroutines? Meh. Software transactional memory and green threads? Heck yeah.
An actual type system? Chef's kiss.
Scott Wlaschin from the F# world has written and talked extensively about F# for "boring" software. It works equally well in Haskell. You don't need to use type-level meta programming to spit out a basic service.
Monads are a great honking idea, btw.
Please don't, at least not mimicking the "edgy 13 year old thinks it comes across as cool" tone.
Goroutines are green threads.
Just fucking use the language appropriate to the task you're trying to accomplish.
Just fucking use the language your colleagues can understand and support for the next few years.
Just fucking use the language with the framework and tooling you need to get your job done efficiently and effectively, and one at the appropriate level of abstraction for the project.
Just fucking use the language which AI agents can read and write well, because we're in the End Times and this stuff matters.
Just fucking use the language with great testing and CI/CD support because you'll be spending longer supporting your code than writing it.
The skill is choosing well, and a key realisation is that it's never a one-size-fits-all thing. That's why an article like this is less than helpful, gosh darn it.
Go's Webassembly story is a joke at the moment, so no.
Yeah, C is king right now.
> necessary for a CRUD app that does maybe forty requests a second.
That's a DoS attack in the python world.
> No base image CVE alerts every Tuesday.
Citation needed. Go's fat binaries and big stdlib cause most enterprise-mandated CVE scanners to light up with zillions of false positives constantly, because too much shit is present in the binary.
Logger package technically could speak protobuf over gopher, even though you use it to write text to syslog? Congrats, gopher and protobuf ecosystems are compiled in, with their vulnerabilities! Multiply that by every single golang binary anywhere in your system (seriously, I was getting CVE alerts for un-hardened stdlib cryptography in a 50loc file copying backup tool that could have been a shell script, and audio format conversion buffer underflow CVEs for Traefik, and many many more for months) and it adds up to a pain in the ass.
And heaven help you if you do actually have a vuln in third-party software that needs to be patched without an upstream fix (usually because "upstream doesn't make distributions on golang-$LATEST or $TRANSITIVE-$LATEST and they have a roadmap item to do that next millenium"). You can't install an updated transitive and fix it, you have to recompile and somehow distribute the whole thing. Doing that is never as simple as "go build" for big projects: the remaining 20% of build/toolchain needs that Go itself doesn't cover are inevitably fulfilled by the same pile of it-works-on-the-maintainer's-machine rickety Makefile bullshit we always had, but without even the sanity and conventions of autoconf and friends--and yeah, building others' Go projects in anger/in a hurry is enough of a pain in the ass that it makes me miss fucking autoconf.
On balance, I like Go. And there's a lot to hate about dynamic linking and package manager hell. But Go's approach is definitely not without its drawbacks in the CVE/security space.
For web stuff, I enjoy https://htmx.org
so Go + Templ + HTMX (aka the GOTH stack)
Or, if you prefer more of a power tools feel, then HTMX and Raku in functional style (https://harcstack.org) maybe to your liking. Which I call the Crotch Rocket of the programming world.
That looks like a fun stack—especially the Raku—but have you ever actually deployed anything using it?
Here are some:
It's fairly early days ... but a couple of other Raku community sites are taking it up.
Basically everything mentioned in the article, and more, is served better with Java or C#.
> Hey, dipshit. You know what compiles in two seconds, deploys as a single binary, and doesn't shit itself when a transitive dependency gets yanked from npm at 3am?
OCaml?
Go, similar to Rust, has a horrible ecosystem, IMHO. I want to like it, but they already broke backwards compatibility with older systems (try to get the Go compiler running on a slightly older OS X, f.ex.), and for a compiler that's a no-go to me.
I regret to inform you, but the post was not about maintaining old systems, it was about simplicity in creating services using a language purpose-designed for creating services. The modern underlying os/docker image was kind of a baked-in assumption.
The post was about Go and uses the "The boring choice is the right choice." point at the end. But a compiler that's so quick to abandoned previously perfectly fine supported systems, and basically is bleeding edge, is anything but the "boring right choice". I personally prefer long term stability in the toolchains I use for my projects at least.
What language/toolchain/platform are you talking about because C/C++/Python/... all have the same issues?
Bootstrapping a modern/ up-to-date C/C++ compiler works, getting an up-do-date Python onto the system works too. No such issues with either of those. But try the same with Go or Rust and you hit nasty roadblocks (in both cases the older OS was previously supported just fine.)
Isn't that the same thing that Homebrew does? It only has CI for the last couple of Mac OS X versions. Seems like this is more of an issue with Mac OS X architecture than Go.
Dunno about Homebrew (haven't used that since many years for various reasons), but Apple is certainly pushing things in a way that makes 3rd party developers quickly abandon old systems too. That's true. At least lots of 3rd party developers are very quick to give up if their new Xcode will not cooperate.
Lately using go is becoming attractive to me also this seems a sign to take the plunge
How well does Go handle GPGPU?
With agentic coding you can just use Rust. AI agents are really good at Rust and the good error message the compiler and or borrow checker gives makes it easy for the AI agent to adjust its code and fix it.
For non agentic coding Go has terrible error handling. It does not have exceptions or monadic error handling. Some call that a feature but many avoid Go or that specific reason. This will not change because that debate has been settled so if you can live with if err != nil after each function call (almost) then you are fine.
Things that is beautiful with Go are: * Its simplicity * Superb cross compilation support and excellent support for many different OS/arch combos. Not sure if anything comes close to this level of easy way of compiling to Many targets and target support.
> Not sure if anything comes close to this level of easy way of compiling to Many targets and target support.
Cross-compilation has been a standard feature of every compiled language toolchain for the past decade at least.
Also a big plus for Go: no async.
Exactly, I don’t know why we are even arguing over this anymore. People basically don’t write code today so why not use the best languages?
Go is handicapped in significant ways which were specifically designed so that it was easier for humans to write. People pushing for Go code in this age either don’t understand the future or the past
After a few years of Kotlin, I recently ran into what I consider to be some shortcomings here as well, with respect to returning errors. As we know, Kotlin does not have checked exceptions. Ok, but ...
The KEEP for Result<T> goes into details, but basically there are a few ways of handling return values + errors:
1) throw exception if something goes wrong
2) return null if something goes wrong (stdlib XXXorNull)
3) Use Result<T> in some cases, its error type is not paramerized and catches CancellationException
4) Use Arrow if Result<T> doesn't meet your needs
5) Return Sealed Classes / Interfaces with all the possibilities.
Right now, I am going the Sealed Class interface route, but its such a verbose pain in the ass, so I only use it at certain levels of abstraction (like from a Repository, or library API, etc).
The code I needed to write was calling into okio, and it was not straightforward to figure out what kinds of exceptions would be thrown by the JVM layer underneath (docs just say IOException, but sublasses can be thrown).
Its sad to see they still haven't figured this out. Rich Errors was mentioned a year ago but its not even in preview yet. Its also not clear how it will work with Java interop.
I personally avoid Go because if the if err != nil But I love the fact they have no exceptions. Rust handles this very well but it's way more complicated in general. I feel like we miss a middle ground. Maybe when Gleam becomes really popular, it could be the one?
I still can understand the attraction with having the same language and codebase if you need a deeper level of interactivity on the frontend. That's where Node shines.
Go is simple, but Rails is pragmatic for web stuff.
A dig at Django's ORM seems hilarious. I wonder how many SQL injection vulnerabilities are written daily by Go devs.
sqlc is pretty darn great but it's no comparison to Django. Django has its own problems and I would love for it to take some lessons from sqlc.
Really hate we don't have proper type hints for Django.
Is django the best backend batteries included framework at the moment?
For the ORM/schema evolution part at least imo. And iommi is written for it so that is also a big boost (I'm one of the authors of iommi :P)
I wrote about this here [1]
The big idea with LLMs is consistent references in the training corpus produced cheddar output by the language model during inference.
Go is an amazing language for language models because it's actually quite boring predictable while packing a lot of powerful distractions with a world class tool chain supported by Google and strong std library as well.
As a programmer I actually hated writing Go... and wanted to write Rust; but using coding agents makes me appreciate writing Go more.
I can get consistent results out while having concurrency cross compilation and predictability.
https://jry.io/writing/ai-makes-golang-one-of-the-best-langu...
If you like a sort of weakly-typed version of Python or PHP, use Go. As the article points out it can be good for web forms. Not all development is web forms.
how is go a weakly typed version of python or php? or even more generic, how is go weakly typed? explain please.
I suspect that this is breaking type systems out onto separate axes: strong/weak and static/dynamic. Many consider Python to be strong/dynamic.
depending on the definition of static and dynamic, go could arguably be described as static (personally, i prefer declarative, as static is generally defined as something that i understand to be true for C but not for go), but that still does not make it weak.
I'm saying Go has a weak type system. This was apparently an intentional choice by the authors. Go doesn't even have sum types, which are an extremely basic, foundational feature of type systems. Even C had an equivalent in its union types.
"Weak type system" means that there are many restrictions on what constitutes a valid program that can't be expressed in Go, that can be expressed in languages with stronger type systems, like Rust, Haskell, or even Java and C#.
ok, fair enough, what actually interests me is why in your opinion it is weak.
in my understanding, weak typing comes from values being interpreted differently or are silently converted based on how they are used. php taking strings with numeric values when used in a math operation is a form of weak typing.
if that is not what you mean then you are using a different definition for weak typing. that's ok, i don't want to argue about what is the right definition, but i am interested in the definition you are using.
I was just using "weak" in the ordinary English sense, as in "not powerful". But you're right, that's confusing because of existing terminology.
I should have said that Go has a less expressive type system, i.e. the expressivity of its type system is weak.
It has no sum types, no algebraic data types, no real pattern matching, and limited generic constraints. And of course if we compare it to much more expressive type systems, which is a bit unfair because none of them are mainstream, it has no higher-kinded types, no dependent types, and no refinement types.
As for "true" weak typing, the idiomatic use of `any` (`interface{}`) in Go comes pretty close. While languages with more expressive type systems will often have some way to express that general concept, it's typically more constrained, more friendly to static checks, and less unsafe, because of the some of the other properties I mentioned, such as pattern matching where the compiler will warn you if you're not properly checking a value before unsafely accessing it.
The reason for my comment about Python and PHP is because to me, programming in Go feels little different in programming in those languages along with one of the bolted-on static checkers they have - you don't get anywhere close to the same level of static guarantee of correctness of programs. It's difficult to implement Yaron Minsky's adage "make invalid states unrepresentable" in Go's type system, and that's unfortunate.
Strange position to take. Python and Go are both duck typed but given Go has compile time type checking it is providing another layer of safety. Could you add more detail supporting your stance?
I expressed that poorly. I'm saying Go adds a weak type system. Too weak for its own good. No sum types is the first dealbreaker. Even C had unions.
Rust (just swap the linker) > Go. Thank you, rewrite the article please.
I know it doesn't matter, but...
...who invented this letter-casing convention?
why is the name of a module lower-cased
but the names of functions accessed via its namespace upper-cased?
how does this make sense?
Upper case symbols in a module are exported. Everything else should be lower case.
Oh; in that case the lowly Javascript/typescript does this so much cleaner with the explicit `export` keyword. "Explicit is better than implicit."
Go language is explicit: identifiers starting with a capital letter are exported.
https://go.dev/ref/spec#Exported_identifiers
I find “locality of scope” to be extremely significant fact about a variable, and so I like seeing signs of it at one glance. The . as well for everything tied in a class and the go convention of shorter names for function locals also support this awarenes.
I’m so tired of AI slop baitposting, guys.
> the race detector will tell you when you screwed up.
lol
> if err != nil {
lmao
> defer rows.Close()
Oh dear
...
I'm only poking fun, I'll take a go backend any day over most of the alternatives. Same goes for CLI tools.
Hey dipshit,
Maybe I develop games. Maybe I develop IoT devices. I might even be working in a high-stakes environment where formal verification is needed, who knows.
Whatever the case may be, we all have our reasons for choosing certain technologies. Not everyone is building run-of-the-mill 'backends' after all.
So please, let's stuff that neckbeardy arrogance away. It serves no purpose and distracts from the discussion.
Thanks.
P. s. I develop my backends in go.
> and doesn't shit itself when a transitive dependency gets yanked from npm
For non-trivial golang apps you're still gonna find npm in the mix. I recently packaged forgejo, yopass, and a few others, and if you don't have `npm` on the build machine, the resulting daemon won't serve the front end.
archived: https://nonogra.ph/just-fucking-use-go-05-08-2026
I know this is nitpicky, but whenever I see Go code I see those capitalized function or variable names and know: “aha, these were imported from another file; or will be exported later” and I think to my self: “why? oh why is that relevant information for my at this point in the code?” and I just think about what kind of a weird ill thought out design decision that was, just to save authors from writing an “export” keyword, and further judge the rest of the language predicting it must have more weird design decisions in it.
I've worked on large Go codebases on large teams at large orgs with large dreams. That garbage us straight up unmaintainable in the large and nobody can convince me otherwise. I have deep knowledge of the language, platform, used it for over a decade. It sucks.
It sucks because?
Account created one hour ago...
My experience is directly counter. I've worked in half a dozen very large orgs and moving from interpreted languages to Go in each one made the system easier to reason about, more maintainable, and easier to onboard new team members. Go strikes a balance between all the competing priorities that has, in my personal experience, improved the engineering velocity across half a dozen companies comprised of hundreds of developers each. For two organizations, they made very coupled and hard to reason about code bases, but those were vastly easier to reason about than had they been written in, say, python or ruby.
Compared to a language like Python or Ruby, yes it's better for larger programs. Compared to the likes C# or Java? Not a chance.
Most former java devs I have worked with prefer Go after a year or so. Sample size is probably around 40 java devs leading to about 35ish Converts.
However, I have only worked with about 5 or so former C# and while they liked Go, they missed C#. Only one preferred Go.
C# is on my shortlist to pick up.
I think this entirely depends on what kind of Java is being written. If you are Java developer on say Spring stack, moving to Go never ends up well in my experience. On several projects I worked on there was a push (strong word, more like investigation/POC) to start writing some services etc in Go and there was almost universal rejection at the end. So I think there is more to just language selection/preference but more like an entire ecosystem depending on what you are hacking
I would like someone to explain me Go. Really, I will use strong words but that's really what I feel.
The syntax changes a lot from the C one, and I can't see any reason for it. To me, it looks unstructured, with the lack of colons for example. It ignores memory safety, it feels like it ignored all of the typing system research since C, no discriminated union, and structures and types in general are heavy to write. It encourages bad patterns, errors out on mundane things like an unused variable, forces you to handle errors with a lot of code while not catching much more than C in terms of bug-prone practices. The package/module system is a nightmare for contributing to open source projects. Modifying a dependency to find a bug is very hard, even swapping a dependency (version) is annoying.
And what do you get from all of this compared to C? A garbage collector, tuples, and goroutines. No metaprogramming (aside from generics, and that was a whole story), interop with C is limited. To me, it looks like it does not focus on the algorithms, but on the code implementation, which is imo what leads us into poor programming and missing critical logic flaws, because the logic is buried. I may have forgotten other gripes I got while working with Go, but honestly, if I wanted all of that, I would pick D, at least it interops well with C and has metaprogramming (and has been made earlier, which excuses a little the lack of certain things).
But really, I am open to someone explaining me how they enjoy Go. Because I feel like I should be wrong as I see most people (which, for some of them, I know are clever) praise Go.
Edit: I added modal expressions to make it clear that it is my opinion.
I like it because I have more control over the size and layout of my memory structures than many GC languages, and the goroutine/channel data flow design model lets me use all the cores pretty evenly without having to worry about mutexes or subtleties. Pretty easy to get into the 100k request per second performance regime without special tweaking. I tend to either write long lived servers where the performance per container directly affects the cost, or analytics sort of Calais where I want my laptop to use 1/2 or 3/4 of its cores and get a faster answer from scraping 10M whatevers.