Google's networking services keep being writen in Java/Kotlin, C++, and nowadays Rust.
I would criticize Go from the point of view of more modern languages that have powerful type systems like the ML family, Erlang/Elixir or even the up and coming Gleam. These languages succeed in providing powerful primitives and models for creating good, encapsulating abstractions. ML languages can help one entirely avoid certain errors and understand exactly where a change to code affects other parts of the code — while languages like Erlang provided interesting patterns for handling runtime errors without extensive boilerplate like Go.
It’s a language that hobbles developers under the aegis of “simplicity.” Certainly, there are languages like Python which give too much freedom — and those that are too complex like Rust IMO, but Go is at best a step sideways from such languages. If people have fun or get mileage out of it, that’s fine, but we cannot pretend that it’s really this great tool.
Go release date: 2012
ML: 1997
". They are likely the two most difficult parts of any design for parametric polymorphism. In retrospect, we were biased too much by experience with C++ without concepts and Java generics. We would have been well-served to spend more time with CLU and C++ concepts earlier."
https://go.googlesource.com/proposal/+/master/design/go2draf...
Cargo is amazing, and you can do amazing things with it, I wish Go would invest in this area more.
Also funny you mention Python, a LOT of Go devs are former Python devs, especially in the early days.
The language sits in an awkward space between rust and python where one of them would almost always be a better choice.
But, google rose colored specs...
Rust simply doesn’t cut it for me. I’m hoping Roc might become this, but I’m not holding my breath.
Go _excels_ at API glue. Get JSON as string, marshal it to a struct, apply business logic, send JSON to a different API.
Everything for that is built in to the standard library and by default performant up to levels where you really don't need to worry about it before your API glue SaaS is making actual money.
The other jarring example of this kind of deferring logical thinking to big corps was people defending Apple's soldering of memory and ssd, specially so on this site, until some Chinese lad proved that all the imagined issues for why Apple had to do such and such was bs post hoc rationalisation.
The same goes with Go, but if you spend enough time, every little while you see the disillusionment of some hardcore fans, even from the Go's core team, and they start asking questions but always start with things like "I know this is Go and holy reasons exists and I am doing a sin to question but why X or Y". It is comedy.
It is infuriating because it is close to being good, but it will never get there - now due to backwards compatibility.
Also Rob Pike quote about Go's origins is spot on.
Go has a good-enough standard library, and Go can support a "pile-of-if-statements" architecture. This is all you need.
Most enterprise environments are not handled with enough care to move beyond "pile-of-if-statements". Sure, maybe when the code was new it had a decent architecture, but soon the original developers left and then the next wave came in and they had different ideas and dreamed of a "rewrite", which they sneakily started but never finished, then they left, and the 3rd wave of developers came in and by that point the code was a mess and so now they just throw if-statements onto the pile until the Jira tickets are closed, and the company chugs along with its shitty software, and if the company ever leaks the personal data of 100 million people, they aren't financially liable.
Every piece of code looks the same and can be automatically, neutrally, analysed for issues.
It just feels sloppy and I'm worried I'm going to make a mistake.
Its annoying to need to think about whether I’m working with an interface type of concrete type.
And if use pointers everywhere, why not make it the default?
yeah no. you need an acyclic structure to maybe guarantee this, in CPython. other Python implementations are more normal in that you shouldn't rely on finalizers at all.
> It is possible (though not recommended!) for the __del__() method to postpone destruction of the instance by creating a new reference to it. This is called object resurrection.
[0]: https://docs.python.org/3/reference/datamodel.html#object.__...
Reading: cyclic GC, yes, the section I linked explicitly discusses that problem, and how it’s solved.
Yes, yes. Hence the words "almost" and "pretty much". For exactly this reason.
Some critique is definitely valid, but some of it just sounds like they didn't take the time to grasp the language. It's trade offs all the way. For example there is a lot I like about Rust, but still no my favorite language.
That said I really wish there was a revamp where they did things right in terms of nil, scoping rules etc. However, they've commited to never breaking existing programs (honorable, understandable) so the design space is extremely limited. I prefer dealing with local awkwardness and even excessive verbosity over systemic issues any day.
I don't think the article sounds like someone didn't take the time to grasp the language. It sounds like it's talking about the kind of thing that really only grates on you after you've seriously used the language for a while.
But I can't help but agree with a lot of points in this article. Go was designed by some old-school folks that maybe stuck a bit too hard to their principles, losing sight of the practical conveniences. That said, it's a _feeling_ I have, and maybe Go would be much worse if it had solved all these quirks. To be fair, I see more leniency in fixing quirks in the last few years, like at some point I didn't think we'd ever see generics, or custom iterators, etc.
The points about RAM and portability seem mostly like personal grievances though. If it was better, that would be nice, of course. But the GC in Go is very unlikely to cause issues in most programs even at very large scale, and it's not that hard to debug. And Go runs on most platforms anyone could ever wish to ship their software on.
But yeah the whole error / nil situation still bothers me. I find myself wishing for Result[Ok, Err] and Optional[T] quite often.
In what universe?
Is it the best or most robust or can you do fancy shit with it? No
But it works well enough to release reliable software along with the massive linter framework that's built on top of Go.
I'd say that it's entirely the other way around: they stuck to the practical convenience of solving the problem that they had in front of them, quickly, instead of analyzing the problem from the first principles, and solving the problem correctly (or using a solution that was Not Invented Here).
Go's filesystem API is the perfect example. You need to open files? Great, we'll create
func Open(name string) (*File, error)
function, you can open files now, done. What if the file name is not valid UTF-8, though? Who cares, hasn't happen to me in the first 5 years I used Go.Stuff like this matters a great deal on the standard library level.
It's far better to get some � when working with messy data instead of applications refusing to work and erroring out left and right.
[]Rune is for sequences of UTF characters. rune is an alias for int32. string, I think, is an alias for []byte.
Score another for Rust's Safety Culture. It would be convenient to just have &str as an alias for &[u8] but if that mistake had been allowed all the safety checking that Rust now does centrally has to be owned by every single user forever. Instead of a few dozen checks overseen by experts there'd be myriad sprinkled across every project and always ready to bite you.
Nothing? Neither Go nor the OS require file names to be UTF-8, I believe
You can do something like WTF-8 (not a misspelling, alas) to make it bidirectional. Rust does this under the hood but doesn’t expose the internal representation.
I've said this before, but much of Go's design looks like it's imitating the C++ style at Google. The comments where I see people saying they like something about Go it's often an idiom that showed up first in the C++ macros or tooling.
I used to check this before I left Google, and I'm sure it's becoming less true over time. But to me it looks like the idea of Go was basically "what if we created a Python-like compiled language that was easier to onboard than C++ but which still had our C++ ergonomics?"
I agree.
The Go std-lib is fantastic.
Also no dependency-hell with Go, unlike with Python. Just ship an oven-ready binary.
And what's the alternative ?
Java ? Licensing sagas requiring the use of divergent forks. Plus Go is easier to work with, perhaps especially for server-side deployments.
Zig ? Rust ? Complex learning curve. And having to choose e.g. Rust crates re-introduces dependency hell and the potential for supply-chain attacks.
you can go `uv run script.py` and it'll automatically fetch the libraries and run the script in a virtual environment.
Still no match for Go though, shipping a single cross-compiled binary is a joy. And with a bit of trickery you can even bundle in your whole static website in it :) Works great when you're building business logic with a simple UI on top.
You really come to appreciate when these batteries are included with the language itself. That Go binary will _always_ run but that Python project won't build in a few years.
Yeah, but you still have to install `uv` as a pre-requisite.
And you still end up with a virtual environment full of dependency hell.
And then of course we all remember that whole messy era when Python 2 transitioned to Python 3, and then deferred it, and deferred it again....
You make a fair point, of course it is technically possible to make it (slightly) "cleaner". But I'll still take the Go binary thanks. ;-)
Yes, My favourite is the `time` package. It's just so elegant how it's just a number under there, the nominal type system truly shines. And using it is a treat. What do you mean I can do `+= 8*time.Hour` :D
It's simplistic and that's nice for small tools or scripts, but at scale it becomes really brittle since none of the edge cases are handled
In Go, `int * Duration = error`, but `Duration * Duration = Duration`!
Yeah, these are sagas only, because there is basically one, single, completely free implementation anyone uses on the server-side and it's OpenJDK, which was made 100% open-source and the reference implementation by Oracle. Basically all of Corretto, AdoptOpenJDK, etc are just builds of the exact same repository.
People bringing this whole license topic up can't be taken seriously, it's like saying that Linux is proprietary because you can pay for support at Red Hat..
So you mean all those universities and other places that have been forced to spend $$$ on licenses under the new regime also can't be taken seriously ? Are you saying none of them took advice and had nobody on staff to tell them OpenJDK exists ?
Regarding your Linux comment, some of us are old enough to remember the SCO saga.
Sadly Oracle have deeper pockets to pay more lawyers than SCO ever did ....
I don't know what/which university you talk about, but I'm sure they were also "forced to pay $$$" for their water bills and whatnot. If they decided to go with paid support, then.. you have to pay for it. In exchange you can a) point your finger at a third-party if something goes wrong (which governments love doing/often legally necessary) b) get actual live support on Christmas Eve if needed.
Quote from this article:[1]
*He told The Register that Oracle is "putting specific Java sales teams in country, and then identifying those companies that appear to be downloading and... then going in and requesting to [do] audits. That recipe appears to be playing out truly globally at this point."*
[1] https://www.theregister.com/2025/06/13/jisc_java_oracle/Also, as another topic, Oracle is doing audits specifically because their software doesn't phone home to check licenses and stuff like that - which is a crucial requirement for their intended target demographics, big government organizations, safety critical systems, etc. A whole country's healthcare system, or a nuclear power base can't just stop because someone forgot to pay the bill.
So instead Oracle just visits companies that have a license with them, and checks what is being used to determine if it's in accord with the existing contract. And yeah, from this respect I also heard of a couple of stories where a company was not using the software as the letter of the contract, e.g. accidentally enabling this or that, and at the audit the Oracle salesman said that they will ignore the mistake if they subscribe to this larger package, which most manager will gladly accept as they can avoid the blame, which is questionable business practice, but still doesn't have anything to do with OpenJDK..
This info is actually quite surprising to me, never heard of it since everywhere I know switched to OpenJDK-based alternatives from the get-go. There was no reason to keep on the Oracle one after the licencing shenanigans they tried to play.
Why do these places kept the Oracle JDK and ended up paying for it? OpenJDK was a drop-in replacement, nothing of value is lost by switching...
See link/quote in my earlier reply above.
I’m only a casual user of both but how are rust crates meaningfully different from go’s dependency management?
e.g. iirc. Rust has multiple ways of handling Strings while Go has (to a big extent) only one (thanks to the GC)
The code was on the hot path of their central routing server handling Billions (with a B) messages in a second or something crazy like that.
You're not building Discord, the GC will most likely never be even a blip in your metrics. The GC is just fine.
The issue is that it was a bit outdated in the choice of _which_ things to choose as the one Go way. People expect a map/filter method rather than a loop with off by one risks, a type system with the smartness of typescript (if less featured and more heavily enforced), error handling is annoying, and so on.
I get that it’s tough to implement some of those features without opening the way to a lot of “creativity” in the bad sense. But I feel like go is sometimes a hard sell for this reason, for young devs whose mother language is JavaScript and not C.
Do they? After too many functional battles I started practicing what I'm jokingly calling "Debugging-Driven Development" and just like TDD keeps the design decisions in mind to allow for testability from the get-go, this makes me write code that will be trivially easy to debug (specially printf-guided debugging and step-by-step execution debugging)
Like, adding a printf in the middle of a for loop, without even needing to understand the logic of the loop. Just make a new line and write a printf. I grew tired of all those tight chains of code that iterate beautifully but later when in a hurry at 3am on a Sunday are hell to decompose and debug.
I think it's a bad trade-off, most languages out there are moving away from it
It's just that a ridiculous amount of steps in real world problems can be summarised as 'reshape this data', 'give me a subset of this set', or 'aggregate this data by this field'.
Loops are, IMO, very bad at expressing those common concepts briefly and clearly. They take a lot of screen space, usually accesory variables, and it isn't immediately clear from just seing a for block what you're about to do - "I'm about to iterate" isn't useful information to me as a reader, are you transforming data, selecting it, aggregating it?.
The consequence is that you usually end up with tons of lines like
userIds = getIdsfromUsers(users);
where the function is just burying a loop. Compare to:
userIds = users.pluck('id')
and you save the buried utility function somewhere else.
I got insta rejected in interview when i said this in response to interview panels question about 'thoughts about golang' .
Like they said, 'interview is over' and showed me the (virtual) door. I was stunned lol. This was during peak golang mania . Not sure what happened to rancherlabs .
I quite often see devs introducing them in other languages like TypeScript, but it just doesn't work as well when it's introduced in userland (usually you just end up with a small island of the codebase following this standard).
I know it's mostly a matter of tastes, but darn, it feels horrible. And there are no default parameter values, and the error hanling smells bad, and no real stack trace in production. And the "object orientation" syntax, adding some ugly reference to each function. And the pointers...
It took me back to my C/C++ days. Like programming with 25 year old technology from back when I was in university in 1999.
Many compiled languages are very slow to compile however, especially for large projects, C++ and rust being the usual examples.
In fact this was so surprising to me is that I only found out about it when I wrote code that processed files in a loop, and it started crashing once the list of files got too big, because defer didnt close the handles until the function returned.
When I asked some other Go programmers, they told me to wrap the loop body in an anonymus func and invoke that.
Other than that (and some other niggles), I find Go a pleasant, compact language, with an efficient syntax, that kind of doesn't really encourage people trying to be cute. I started my Go journey rewriting a fairly substantial C# project, and was surprised to learn that despite it having like 10% of the features of C#, the code ended up being smaller. It also encourages performant defaults, like not forcing GC allocation at every turn, very good and built-in support for codegen for stuff like serialization, and no insistence to 'eat the world' like C# does with stuff like ORMs that showcase you can write C# instead of SQL for RDBMS and doing GRPC by annotating C# objects. In Go, you do SQL by writing SQL, and you od GRPC by writing protobuf specs.
It can be also a source of bugs where you hang onto something for longer than intended - considering there's no indication of something that might block in Go, you can acquire a mutex, defer the release, and be surprised when some function call ends up blocking, and your whole program hangs for a second.
Right now it's function scope; if you need it lexical scope, you can wrap it in a function.
Suppose it were lexical scope and you needed it function scope. Then what do you do?
You can just introduce a new scope wherever you want with {} in sane languages, to control the required behavior as you wish.
if (some condition) { defer x() }
When it's lexically scoped, you'd need to add some variable. Not that that happens a lot, but a lexically scoped defer isnt needed often either.Defer a bulk thing at the function scope level, and append files to an array after opening them.
Would be nice to have both options though. Why not a “defer” package?
?
I chuckled
Never had any problems with Go as it makes me millions each year.
Go is a case of the emperor having no clothes. Telling people that they just don’t get it or that it’s a different way of doing things just doesn’t convince me. The only thing it has going for it is a simple dev experience.
Go is a reasonably performant language that makes it pretty straightforward to write reliable, highly concurrent services that don't rely on heavy multithreading - all thanks to the goroutine model.
There really was no other reasonably popular, static, compiled language around when Google came out.
And there still barely is - the only real competitor that sits in a similar space is Java with the new virtual threads.
Languages with async/await promise something similar, but in practice are burdened with a lot of complexity (avoiding blocking in async tasks, function colouring, ...)
I'm not counting Erlang here, because it is a very different type of language...
So I'd say Go is popular despite the myriad of shortcomings, thanks to goroutines and the Google project street cred.
It can’t match it for performance. There’s no mutable array, almost everything is a linked list, and message passing is the only way to share data.
I primarily use Elixir in my day job, but I just had to write high performance tool for data migration and I used Go for that.
And lists are slower than arrays, even if they provide functional guarantees (everything is a tradeoff…)
That said, pretty much everything else about it is amazing though IMHO and it has unique features you won’t find almost anywhere else
And even without types (which are coming and are looking good), Elixir's pattern matching is a thousands times better than the horror of Go error handling
For ML/data: python
For backend/general purpose software: Java
The only silver bullet we know of is building on existing libraries. These are also non-accidentally the top 3 most popular languages according to any ranking worthy of consideration.
Nonetheless, Java has eased the psvm requirements, you don't even have to explicitly declare a class and a void main method is enough. [1] Not that it would matter for any non-script code.
----- https://openjdk.org/jeps/512 -----
First, we allow main methods to omit the infamous boilerplate of public static void main(String[] args), which simplifies the Hello, World! program to:
class HelloWorld {
void main() {
System.out.println("Hello, World!");
}
}
Second, we introduce a compact form of source file that lets developers get straight to the code, without a superfluous class declaration: void main() {
System.out.println("Hello, World!");
}
Third, we add a new class in the java.lang package that provides basic line-oriented I/O methods for beginners, thereby replacing the mysterious System.out.println with a simpler form: void main() {
IO.println("Hello, World!");
}
PHP's frameworks are fantastic and they hide a lot from an otherwise minefield of a language (though steadily improved over the years).
Both are decent choices if this is what you/your developers know.
But they wouldn't be my personal first choice.
Local environments are not tied to IDEs at all, but you are doing yourself a disservice if you don't use a decent IDE irrespective of language - they are a huge productivity boost.
And are you stuck in the XML times or what? Spring Boot is insanely productive - just as a fact of matter, Go is significantly more verbose than Java, with all the unnecessary if errs.
Which Google uses far more commonly than Go, still to this day.
My experience is mostly with C#, but async/await works very well there in my experience. You do need to know some basics there to avoid problem, but that's the case for essentially every kind of concurrency. They all have footguns.
The change from Java 8 to 25 is night and day. And the future looks bright. Java is slowly bringing in more language features that make it quite ergonomic to work with.
I have no desire to go back to Java no matter how much the language has evolved.
For me C# has filled the void of Java in enterprise/gaming environments.
Like, there are 10 million Java devs, there is a whole lot of completely brand new development going in any language, let alone in such a huge one.
It's fast enough, easy enough (being very similar now to TypeScript), versatile enough, well-documented (so LLMs do a great job), broad and well-maintained first party libraries, and the team has over time really focused on improving terseness of the language (pattern matching and switch expressions are really one thing I miss a lot when switching between C# and TS).
EF Core is also easily one of the best ORMs: super mature, stable, well-documented, performant, easy to use, and expressive. Having been in the Node ecosystem for the past year, there's really no comparison for building fast with less papercuts (Prisma, Drizzle, etc. all abound with papercuts).
It's too bad that it seems that many folks I've chatted with have a bad taste from .NET Framework (legacy, Windows only) and may have previously worked in C# when it was Windows only and never gave it another look.
It was actually really good for the time and lightyears ahead of whatever Flash was doing.
But people rather used all kinds of hacks to get Flash working on Linux and OSX rather than use Moonlight.
Which means if you write C#, you'll encounter a ton of devs who come from an enterprise, banking or govt background, who think doing a 4 layer enterprise architecture with DTOs and 5 line classes is the only way you can write a CRUD app, and the worst of all you'll se a ton of people who learned C# in college a decade ago and refuse to learn anything else.
EF is great, but most people use it because they don't have to learn SQL and databases.
Blazor is great, but most people use it because they don't want to learn Frontend dev, and JS frameworks.
"Modern C#" (if we can differentiate that) has a lot of nice amenities for modeling like immutable `record` types and named tuples. I think where EF really shines is that it allows you to model the domain with persistence easily and then use DTOs purely as projections (which is how I use DTOs) into views (e.g. REST API endpoints).
I can't say for the broader ecosystem, but at least in my own use cases, EFC is primarily used for write scenarios and some basic read scenarios. But in almost all of my projects, I end up using CQRS with Dapper on the read side for more complex queries. So I don't think that it's people avoiding SQL; rather it's teams focused on productivity first.
WRT to Blazor, I would not recommend it in place of JS except for internal tooling (tried it at one startup and switched to Vue + Vite). But to be fair, modern FE development in JS is an absolute cluster of complexity.
That said, if on the JVM, just use Kotlin.
Go, with all its faults, tries very hard to shun complexity, which I've found over the years to be the most important quality a language can have. I don't want a language with many features. I want a language with the bare essentials that are robust and well designed, a certain degree of flexibility, and for it to get out of my way. Go does this better than any language I've ever used.
> Go, with all its faults, tries very hard to shun complexity
The whole field is about managing complexity. You don't shun complexity, you give tools to people to be able to manage it.
And Go goes the low end of the spectrum, of not giving enough features to manage that complexity -- it's simplistic, not simple.
I think the optimum as actually at Java - it is a very easy language with not much going on (compared to, say, Scala), but just enough expressivity that you can have efficient and comfortable to use libraries for all kind of stuff (e.g. a completely type safe SQL DSL)
Complexity exists in all layers of computing, from the silicon up. While we can't avoid complexity of real world problems, we can certainly minimize the complexity required for their solutions. There are an infinite amount of problems caused primarily by the self-induced complexity of our software stacks and the hardware it runs on. Choosing a high-level language that deliberately tries to avoid these problems is about the only say I have in this matter, since I don't have the skill nor patience to redo decades of difficult work smarter people than me have done.
Just because a language embraces simplicity doesn't mean that it doesn't provide the tools to solve real world problems. Go authors have done a great job of choosing the right set of trade-offs, unlike most other language authors.
Every single piece of Go 1.x code scraped from the internet and baked in to the models is still perfectly valid and compiles with the latest version.
Could you quote which paragraph you're talking about?
AFAIK, interoperability with C++ code is just one of their explicit goals; they only place that as the last item in the "Language Goals" section.
First time its assigned nil, second time its overwritten in case there's an error in the 2nd function. I dont see the authors issue? Its very explicit.
After checking for nil, there's no reason `err` should still be in scope. That's why it's recommended to write `if err := foo(); err != nil`, because after that, one cannot even accidentally refer to `err`.
I'm giving examples where Go syntactically does not allow you to limit the lifetime of the variable. The variable, not its value.
You are describing what happens. I have no problem with what happens, but with the language.
The example from the blog post would fail, because `return err` referred to an `err` that was no longer in scope. It would syntactically prevent accidentally writing `foo99()` instead of `err := foo99()`.
bar, err := foo()
if err != nil {
return err
}
if err := foo2(); err != nil {
return err
}
The above (which declares a new value of err scoped to the second if statement) should compile right? What is it that he's complaining about?EDIT: OK, I think I understand; there's no easy way to have `bar` be function-scoped and `err` be if-scoped.
I mean, I'm with him on the interfaces. But the "append" thing just seems like ranting to me. In his example, `a` is a local variable; why would assigning a local variable be expected to change the value in the caller? Would you expect the following to work?
int func(a *MyStruct) {
a = &MyStruct{...}
}
If not why would you expect `a = apppend(a, ...)` to work? bar, err := foo()
if err != nil {
return err
}
if err = foo2(); err != nil {
return err
}
and bar, err := foo()
if err != nil {
return err
}
if err := foo2(); err != nil {
return err
}
He even says as much:> Even if we change that to :=, we’re left to wonder why err is in scope for (potentially) the rest of the function. Why? Is it read later?
My initial reaction was: "The first `err` is function-scope because the programmer made it function-scope; he clearly knows you can make them local to the if, so what's he on about?`
It was only when I tried to rewrite the code to make the first `err` if-scope that I realized the problem I guess he has: OK, how do you make both `err` variable if-scope while making `bar` function-scope? You'd have to do something like this:
var bar MyType
if lbar, err := foo(); err != nil {
return err
} else {
bar = lbar
}
Which is a lot of cruft to add just to restrict the scope of `err`.If someone really doesn't like the reuse of err, there's no reason why they couldn't create separate variable, e.g. err_foo and err_foo2. There's not no reason to not reuse err.
I think you may need to re-read. My point is that it DOES change the value in the caller. (well, sometimes) That's the problem.
:^/
> The standard library does that. fmt.Print when calling .String(), and the standard library HTTP server does that, for exceptions in the HTTP handlers.
Apart from this most doesn't seem that big of a deal, except for `append` which is truly a bad syntax. If you doing it inplace append don't return the value.
(I realise this isn’t who is hiring, but email in bio)
myfunc(arg: string): Value | Err
I really try not to throw anymore with typescript, I do error checking like in Go. When used with a Go backend, it makes context switching really easy...
Talk about hyperbole.
I don't really care if you want that. Everyone should know that that's just the way slices work. Nothing more nothing less.
I really don't give a damn about that, i just know how slices behave, because I learned the language. That's what you should do when you are programming with it (professionally)
For anyone interested, this article explains the fundamentals very well, imo: https://go.dev/blog/slices-intro
You really don't see why people would point a definition that changes underneath you out as a bad definition? They're not arguing the documentation is wrong.
"Consistent" is necessary but not sufficient for "good".
Just like every PHP coder should know that the ternary operator associativity is backwards compared to every other language.
If you code in a language, then you should know what's bad about that language. That doesn't make those aspects not bad.
type
StatusCodes = (Success, Ongoing, Done)
Go in 2025, type StatusCodes int
const (
Success StatusCodes = iota
Ongoing
Done
)
If you prefer, I can provide the same example in C, C++, D, Java, C#, Scala, Kotlin, Swift, Rust, Nim, Zig, Odin.
Ok we get it, you want something fancier. Well, you didn't get it. Deal with it. Go has other problems (as pointed out by the OP). I really don't understand how people could care so much about this enum thing. Yes, Rust enums are great, but they are just completely different. Why would I ever compare them and waste energy on that? Different designers, different decisions.
Here's the accompanying playground: https://go.dev/play/p/Kt93xQGAiHK
If you run the code, you will see that calling read() on ControlMessage causes a panic even though there is a nil check. However, it doesn't happen for Message. See the read() implementation for Message: we need to have a nil check inside the pointer-receiver struct methods. This is the simplest solution. We have a linter for this. The ecosystem also helps, e.g protobuf generated code also has nil checks inside pointer receivers.
First one - you have an address to a struct, you pass it, all good.
Second case: you set address of struct to "nil". What is nil? It's an address like anything else. Maybe it's 0x000000 or something else. At this point from memory perspective it exists, but OS will prevent you from touching anything that NULL pointer allows you to touch.
Because you don't touch ANYTHING nothing fails. It's like a deadly poison in a box you don't open.
Third example id the same as second one. You have a IMessage but it points to NULL (instead NULL pointing to deadly poison).
And in fourth, you finally open the box.
Is it magic knowledge? I don't think so, but I'm also not surprised about how you can modify data through slice passing.
IMO the biggest Go shortcoming is selling itself as a high level language, while it touches more bare metal that people are used to touch.
It turns out to be nothing but a misunderstanding of what the fmt.Println() statement is actually doing. If we use a more advanced print statement then everything becomes extremely clear:
package main
import (
"fmt"
"github.com/k0kubun/pp/v3"
)
type I interface{}
type S struct{}
func main() {
var i I
var s *S
pp.Println(s, i) // (*main.S)(nil) nil
fmt.Println(s == nil, i == nil, s == i) // true true false
i = s
pp.Println(s, i) // (*main.S)(nil) (*main.S)(nil)
fmt.Println(s == nil, i == nil, s == i) // true false true
}
The author of this post has noted a convenience feature, namely that fmt.Println() tells you the state of the thing in the interface and not the state of the interface, mistaken it as a fundamental design issue and written a screed about a language issue that literally doesn't exist.Being charitable, I guess the author could actually be complaining that putting a nil pointer inside a nil interface is confusing. It is indeed confusing, but it doesn't mean there are "two types" of nil. Nil just means empty.
What are you trying to clarify by printing the types? I know what the types are, and that's why I could provide the succinct weird example. I know what the result of the comparisons are, and why.
And the "why" is "because there are two types of nil, because it's a bad language choice".
I've seen this in real code. Someone compares a variable to nil, it's not, and then they call a method (receiver), and it crashes with nil dereference.
Edit, according to this comment this two-types-of-null bites other people in production: https://news.ycombinator.com/item?id=44983576
There aren't two types of nil. Would you call an empty bucket and an empty cup "two types of empty"?
There is one nil, which means different things in different contexts. You're muddying the waters and making something which is actually quite straightforward (an interface can contain other things, including things that are themselves empty) seem complicated.
> I've seen this in real code. Someone compares a variable to nil, it's not, and then they call a method (receiver), and it crashes with nil dereference.
Sure, I've seen pointer-to-pointer dereferences fail for the same reason in C. It's not particularly different.
It's not about Printf. It's about how these two different kind of nil values sometimes compare equal to nil, sometimes compare equal to each other, and sometimes not
Yes there is a real internal difference between the two that you can print. But that is the point the author is making.
Go had some poor design features, many of which have now been fixed, some of which can't be fixed. It's fine to warn people about those. But inventing intentionally confusing examples and then complaining about them is pretty close to strawmanning.
It's sort of a known sharp edge that people occasionally cut themselves on. No language is perfect, but when people run into them they rightfully complain about it
But everyone knows in their heart of hearts that a few small language warts definitely don't outweigh Go's simplicity and convenience. Do I wish it had algebraic data types, sure, sure. Is that a deal-breaker, nah. It's the perfect example of something that's popular for a reason.
It is easily one of the most productive languages. No fuss, no muss, just getting stuff done.
It's a shame because it is just as effective as pissing in the wind.
Of course, by your reasoning this also means you yourself have designed a language.
I'll leave out repeating your colorful language if you haven't done any of these things.
:Error variable Scope -> Yes can be confusing at the beginning, but if you have some experience it doesnt really matter. Would it be cool to scope it down?`Sure, but it feels like here is something blown up to an "issue" where i would see other things to alot more important for the go team to revisit. Regarding the error handling in go, some hate it , some love it : i personally like it (yes i really do) so i think its more a preference than a "bad" thing.
:Two types of nil -> Funny, i never encountered this in > 10 years of go with ALOT of work in pointer juggling, so i wonder in which reality this hits your where it cant be avoided. Tho confusing i admit
:It’s not portable -> I have no opinion here since i work on unix systems only and i have my compiled binaries specific shrug dont see any issue here either.
:append with no defined ownership -> I mean... seriously? Your test case, while the results may be unexpected, is a super wierd one. Why you you append a mid field, if you think about what these functions do under the hood your attemp actualyl feels like you WANT to procude strange behaviour and things like that can be done in any language.
:defer is dumb -> Here i 100% agree - from my pov it leads to massive resource wasting and in certain situations it can also create strange errors, but im not motivated to explain this - ill just say defer, while it seems usefull, from my pov is a bad thing and should not be used.
:The standard library swallows exceptions, so all hope is lost -> "So all hope is lost" i mean you already left the realm of objectiveness long before tbut this really tops it. I wrote some quite big go applications and i never had a situation where i could not handle an exception simply by adjusting my code in a way that i prevent it from even happening. Again - i feel like someone is just in search of things to complain that could simply be avoided. (also in case someone comes up with a super specific probably once in a million case, well alrways keep in mind that language design doesnt orient on the least occuring thing).
:Sometimes things aren’t UTF-8 -> I wont bother to read another whole article, if its important include an example. I have dealth with different encodings (web crawler) and i could handle all of them.
:Memory use -> What you describe is one of the design decisions im not absolutly happy with, the memory handling. But than, one of my golang projects is an in memory graph storage/database - which in one of my cases run for ~2years without restart and had about 18GB of dataset stored in it. It has a lot of mutex handling (regarding your earlier complain with exxceptions, never had one) and it btw run as backend of a internet facing service so it wasnt just fed internal data.
--------------------
Finally i wanne say : often things come down to personal preference. I could spend days raging about javascript, java, c++ or some other languages, but whatfor? Pick the language that fits your use case and your liking, dont pick one that doesnt and complain about it.
Also , just to show im not just a big "golang is the best" fanboy, because it isnt - there are things to critizize like the previously mentioned memory handling.
While i still think you just created memory leaks in your app, golang had this idea of "arenas" which would enable the code to manage memory partly himself and therefor developt much more memory efficient applications. This has stalled lately and i REALLY hope the go team will pick it up again and make this a stable thing to use. I probably would update all of my bigger codebases using it.
Also - and thats something thats annoying me ALOT beacuse it made me spend alot of hours - the golang plugin system. I wrote an architecture to orchestrate processing and for certain reasons i wanted to implement the orchestrated "things" as plugins. But the plugin system as it is rn can only be described as the torments of hell. I messed with it for like 3 years till i recently dropped the plugin functionality and added the stuff directly. Plugins are a very powerfull thing and a good plugin system could be a great thing, but in its current state i would recommend noone to touch it.
These are just two points, i could list some more but the point i want to get to is : there are real things you can critizize instead of things that you create yourself or that are language design decision that you just dont like. Im not sure if such articles are the rage of someone who just is bored or its ragebait to make people read it. Either way its not helping anyone.
:Two types of nil
Other commenters have. I have. Not everyone will. Doesn't make it good.
:append with no defined ownership
I've seen it. Of course one can just "not do that", but wouldn't it be nice if it were syntactically prevented?
:It’s not portable ("just Unix")
I also only work on Unix systems. But if you only work on amd64 Linux, then portability is not a concern. Supporting BSD and Linux is where I encounter this mess.
:All hope is lost
All hope is lost specifically on the idea of not needing to write exception safe code. If panics did always crash the problem, then that'd be fine. But no coding standard can save you from the standard library, so yes, all hope about being able to pretend panic exits the problem, is lost.
You don't need to read my blog posts. Looking forward to reading your, much better, critique.
> Over the decades I have lost data to tools skipping non-UTF-8 filenames. I should not be blamed for having files that were named before UTF-8 existed.
Umm.. why blame Go for that?
What I intended to say with this is that ignoring the problem if invalid UTF-8 (could be valid iso8859-1) with no error handling, or other way around, has lost me data in the past.
Compare this to Rust, where a path name is of a different type than a mere string. And if you need to treat it like a string and you don't care if it's "a bit wrong" (because it's for being shown to the user), then you can call `.to_string_lossy()`. But it's be more hard to accidentally not handle that case when exact name match does matter.
When exactness matters, `.to_str()` returns `Option<&str>`, so the caller is forced to deal with the situation that the file name may not be UTF-8.
Being sloppy with file name encodings is how data is lost. Go is sloppy with strings of all kinds, file names included.
What has been an issue for me, though, is working with private repositories outside GitHub (and I have to clarify that, because working with private repositories on GitHub is different, because Go has hardcoded settings specifically to make GitHub work).
I had hopes for the GOAUTH environment variable, but either (1) I'm more dumb and blind than I thought I already was, or (2) there's still no way to force Go to fetch a module using SSH without trying an HTTPS request first. And no, `GOPRIVATE="mymodule"` and `GOPROXY="direct"` don't do the trick, not even combined with Git's `insteadOf`.
I really like Go. It scratches every itch that I have. Is it the language for your problems? I don't know, but very possibly that answer is "no".
Go is easy to learn, very simple (this is a strong feature, for me) and if you want something more, you can code that up pretty quickly.
The blog article author lost me completely when they said this:
> Why do I care about memory use? RAM is cheap.
That is something that only the inexperienced say. At scale, nothing is cheap; there is no cheap resource if you are writing software for scale or for customers. Often, single bytes count. RAM usage counts. CPU cycles count. Allocations count. People want to pretend that they don't matter because it makes their job easier, but if you want to write performant software, you better have that those cpu cache lines in mind, and if you have those in mind, you have memory usage of your types in mind.
Golang's biggest shortcoming is the fact that it touches bare metal isn't visible clearly enough. It provides many high level features which makes this ambience of "we got you" but fails on delivering proper education to its users that they are going to have a dirt on their hands.
Take a slice for example: even in naming it means "part of" but in reality it's closer to "box full of pointers" what happens when you modify pointer+1? Or "two types of nil"; there is a difference between having two bytes (simplification), one of struct type and the other of address to that struct and having just a NULL - same as knowing that house doesn't exist and being confident that house exists and saying it's in the middle of the volcano beneath the ocean.
The Foo99 critique is another example. If you'd want to have not 99 loop but 10 billion loops each with mere 10 bytes you'd need 100GiB of memory just to exit it. If you'd reuse the address block you'd only use... 10 bytes.
I also recommend trying to implement lexical scope defer in C and putting them in threads. That's a big bottle of fun.
I think that it ultimately boils down to what kind of engineer one wants to be. I don't like hand holding and rather be left on my own with a rain of unit tests following my code so Go, Zig, C (from low level Languages) just works for me. Some prefer Rust or high level abstractions. That's also fine.
But IMO poking at Go that it doesn't hide abstractions is like making fun of football of being child's play because not only it doesn't have horses but also has players using legs instead of mallets.
Usually, as here, objections to go take the form a technically-correct-but-ultimately-pedantic arguments.
The positives of go are so overwhelmingly high magnitude that all those small things basically don’t matter enough to abandon the language.
Go is good enough to justify using it now while waiting for the slow-but-steady stream of improvements from version to version to make life better.
keyle•3h ago
I say switching to Go is like a different kind of Zen. It takes time, to settle in and get in the flow of Go... Unlike the others, the LSP is fast, the developer, not so much. Once you've lost all will to live you become quite proficient at it. /s
written-beyond•2h ago
ISTG if I get downvoted for sharing my opinion I will give up on life.
aloukissas•1h ago
theshrike79•1h ago
I can still check out the code to any of them, open it and it'll look the same as modern code. I can also compile all of them with the latest compiler (1.25?) and it'll just work.
No need to investigate 5 years of package manager changes and new frameworks.