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.
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.
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?
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.
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.
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.
You can’t provide me any technical answer.
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.
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?
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.
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.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.
Well, they'll call it a design decision, not a bug. I guess I'm being charitable.
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.
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.
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.
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.
Also: why can't we vouch for flagged stories now? This post is actually good, and funny, and the conversations are worth having.
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.
"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?
$ go build -o hello main.go
$ file hello && ldd hello
hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, Go
linux-vdso.so.1 (0x00007f9c7b404000)
libc.so.6 => /usr/lib/x86_64-linux-gnu/libc.so.6 (0x00007f9c7b1ce000)
/lib64/ld-linux-x86-64.so.2 (0x00007f9c7b406000)But nevertheless , if any language provides something like that, win-win for entire ecosystem.
.NET has almost all these upsides, but with a concurrency model (async/await) that is (now) more transferable to other languages.
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)?
Otherwise Java, .NET, Typescript (with possible C++ addons).
For me go is just above c# and both of those are not super high on my list.
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.
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
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.
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.
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 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.
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.
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.
Additional work that is superfluous in other mature programming ecosystems, that don't have the dumb idea to use SCM urls as import paths.
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?
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.
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)
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.