You could think of the Go dev team's last ten years as trying to find the right balance between features (asked for by the expert devs that use Go) and simplicity (a value that the designers hold, but that most expert devs making feature requests don't care about). Generics always felt to me like this dynamic in a nutshell. Lots of good reasons to prefer generics when you need them, and it feels like a nearly ecosystem-killing amount of complexity to implement a type system like Rust on top of Go - there's literally almost no reason to use Go in that case.
Anyway, I like seeing this slight reversion in favor of simplicity, I think it's the right call for where Go's targeted: being a better Java for teams of mid-tier engineers.
It's interesting how simplicity so often gets mistaken for a lack of sophistication. Designing a language for clarity and maintainability is a laudable goal, and so is choosing to use one. Chasing complexity, or reaching for the latest trendy language that lets you "express yourself" in ten different ways to do the same thing, isn't what makes someone an S-tier engineer. Simplicity isn't a concession. It's a hard discipline.
> Designing a language for clarity and maintainability is a laudable goal, and so is choosing to use one. Chasing complexity, or reaching for the latest trendy language that lets you "express yourself" in ten different ways to do the same thing, isn't what makes someone an S-tier engineer.
In exactly the same way, complexity you don't understand the purpose of is often disregarded as complexity for complexities sake.
It's easy to get things simple and wrong, and hard to get things simple and right. And sometimes complexity is there for a good reason, but if you don't work hard to understand, you'll fall into the "simple and wrong" camp often.
One problem with simplicity is that it often moves the complexity elsewhere, particularly if you're designing things that others use.
Brainfuck the language is simple to use and implement. It has only 8 commands.
Brainfuck programs are virtually unreadable, unless you can get your head around it.
The advantage of trying to solve complexity is that if you can solve it, you've solved it once and exactly once for everyone involved.
Thing is, it's not even necessarily true for Go. E.g. that whole `iota` thing is hardly a good example of "simple and obvious" language design compared to enums in... just about everything else. Or we could talk about the difference between a nil interface value vs interface value wrapping nil.
Why would anybody choose C++ over Rust in 2025? The biggest (valid) criticisms against Rust are that it is difficult to learn and use, but C++ is like 1000 times harder. When people say Rust is complicated they’re comparing it to modern GCed languages, not to C++.
> E.g. that whole `iota` thing is hardly a good example of "simple and obvious" language design compared to enums in... just about everything else.
I actually disagree with this specific take. I do agree that iota is an unnecessary bit of cleverness (especially with that name) but I’d much rather a langage have nothing than the pile of lies and garbage that are C enums. At least then it’s not pretending.
Maybe a decade ago I saw a video of Rob Pike telling people they can abandon C++ because golang was good enough. He proclaimed that people can toss out their reams of C++ code and replace it with Go. That never really came to fruition -- even as bad as C++ is.
The only godsend of C is that code written in the 1970's can still be compiled today -- half a century later. You can write code in your 20's in C and still be assured it will still work a half century later in your 70s. (as long as you don't use system libraries which might change.... etc.)
A lot of people complain about Rust compile times. But honestly, I'd rather work in a language that is trying to solve complexity rather than push it off on to the user.
As much as I hate Go, I have to admit that it does sort of fill a niche, or at least it did at the time it was released. If you wanted something less baroque and complex than C++, but still care about performance enough to not want to use an interpreted language, and don’t want to use Java because it’s overly verbose and attracts OOP architecture astronauts, there wasn’t much choice other than Go when it came out.
There’s a very high probability that something like Cockroach would use C++ if Go had never existed, so Rob Pike was sort of right, if you squint. On the other hand, if Cockroach were started today it would probably be written in Rust.
I think you’re missing a couple key benefits to go: compile time and cross arch builds. The story here is best in class by far. It’s so so simple and fast to compile go for 10 architectures and distribute a binary.
Then tell them to write all their code in Brainfuck if it's that easy.
In fact, tell them to write a brainfuck transpiler in brainfuck to transpile go lang to brainfuck to make it easier for you communicate with their native tongue directly.
But honestly if you're a professional programmer, you should constantly be asking yourself how do I reduce complexity for others first, not myself. And that's where golang gets it wrong. Golang asks very specifically first and foremost how do they get their compiler right -- even if it comes at the potential expense of the users.
I mean golang is not brainfuck by any margin, and it is reasonable for what it tries to do. But, in my experience, if you're writing code longer than a page, golang is probably the wrong language.
A core conflict is determining who should hold complexity ("how much" is implied). From an operating systems perspective, I like seL4's approach of being a highly minimal kernel, which necessarily means pushing out significant complexity to userspace to handle. I want to go even further in that direction. Despite the massive complexity spike, kernel minimality has significant benefits for increasing userspace programs' power. I think programming languages are somewhat different. Both operating systems and programming languages are deeply foundational notions of abstraction, and even mesh: Smalltalk[0] or Forth being said to include an operating system isn't outrageous. An operating system exposes languages (e.g. shell commands, syscalls, libraries, services) and a programming language undergirds a system. My suspicion is that it's because an operating system is built up, whereas a programming language is the tool in hand. They're at such different levels of construction that they aren't comparable. I have a refinement, which also ties in something I sneakily avoided earlier: programming languages are like kernels, especially microkernels like seL4. Bundling the whole OS is cheating; the job is already done. But I think there's still a difference. When a kernel is built, the OS isn't far behind; a purpose is set. When a programming language is built, it's made (usually) for generality. The exceptions to this (domain-specific languages) prove my point: they have a purpose in mind. The complexity is still there, but building an operating system seems to provoke long-lasting further abstractions (services, components, libraries) whereas programming is more fragmented in its abstractions (frameworks, libraries, language features). Put another way: operating system developers seek simplicity of the whole system, since that is there scope. It's harder to define a system for programming languages, although it is there (the sum of programs produced), and so optimizations are more local at the expense of global progress.
The problem is further exacerbated by needless complexity being seen as sophistication, and in the end; one's competency level. A lot of today's bloat and bullshit is caused by this attitude. Like a football team full of body builders preoccupied with showing of their muscles and wondering why they're languishing at the bottom of the table.
> there's literally almost no reason to use Go in that case.
I work in C# and C++ day to day now, and in $PREV_JOB I used Go and C++. my go builds on a similar size project were quicker than the linter in my C# project is right now. Go's killer feature IMO is that it's _almost_ scripting level iteration speed.
I too like this feature of go quite a lot. But I think it's supercharged by the major reason I'd pick go for a mid-size team; the language is exceedingly easy to parse, code in and understand even if you're not a genius. I think the testing support and very moderate type support hit a valuable place in development ecosystems -- as you say, you get near instant linting feedback about a lot, and it's also pretty easy to read and write in.
Basically, the mental model required for coding in go is low load. That's a great feature.
> as you say, you get near instant linting feedback about a lot
Sorry, it wasn't clear. I could run `go build && ./myapp` and have my application running quicker than `dotnet format` finishes. Linting in .net is slower than compiling in go.
Agree on everything else. It has it's share of footguns, for sure, but so does every language.
FWIW dotnet format is not a linter, it's just a formatter and yes it is slow (I guess because it's ever so slightly neglected, I'd expect most developers use IDE-provided or Roslyn-provided (for VSC) auto-format instead).
Overall, the tooling could be faster but because it is JIT-based and performs heavy unbound reflection, it's not very amenable to the NAOT compilation in its current form if you are used to frequently running 'dotnet build' and 'dotnet run' where startup latency imposed by JIT is most noticeable. Also keep in mind that both invoke the full build-system. It's closer to what Cargo does than what Go tooling does. Are you using .NET 9 SDK? Another feature I suggest looking at is hot-reload with 'dotnet watch'. It can shorten iteration cycles for doing the back-end work substantially.
Honestly I don’t care why it’s slow. It’s up to dotnet to sell itself. It’s not my problem if dotnet invokes the full toolchain to run an incremental tool - my point is that go’s entire compilation is still quicker and that’s a huge boon.
The tooling itself is not _slow_ (are you intentionally misreading this?). It is slow_er. Why are you using dotnet format? Try CSharpier instead.
It does not matter on CI, it matters locally where you should be using something else.
And of course if you are looking for an excuse to use Go (which is a worse language), fixing this or any other "issue" will not help - there will always be another reason.
Do real organizations actually use C#? Every time we've evaluated it we concluded it's a worse, MS-flavor Java rewrite so we didn't take it too seriously. Does it have any actual advantages over Java?
StackOverflow survey (self reported) for 2024 shows it at #5 leaving out HTML/CSS, and SQL[0]
DevJobsScanner shows it at #4 via scraping job postings[1]
It definitely has heavy adoption; well above Rust and Go despite what we see here on HN.
> Does it have any actual advantages over Java?
The language evolves faster and is more akin to Kotlin than to Java, IMO. The DX is fantastic and there are a few gems like LINQ, Entity Framework, and Roslyn source generators. Modern C# can be very dense yet still highly legible.
C# switch expressions with pattern matching (not switch-case), for example[2], are fantastic.
It has been 8 years since I last wrote any C# code and I still miss LINQ to Objects. It felt so natural building up queries against collections and very readable.
They've made the language much more terse, IMO, taking a lot of cues from TypeScript/JS as well as other languages. Constructs like primary constructors, record types, pattern matching, and destructuring continue to improve ergonomics, IMO.
You are overestimating the voices / anecdotes / evangelists from the internet. I did this as well ... so no offense. When reading HN or Reddit one can easily get the impression that Java, C++ and C# are dead and the world has turned to Rust and Go. Esp. C# is underrated in the real world.
Go was designed explicitly to serve the particular needs of a particular area of software development that allegedly sees more than average development activity. In fact, its designers have expressed some surprise that people found it useful in other areas of programming. Rust, on the other hand, tries to be much more general purpose. Being a jack of all trades master of none, so to speak, is a great technical quality, but without particular focus it is much harder to get the numbers up.
It is quite similar to why Javascript blows all of the other languages out of the water. It would be surprising if Rust had more usage like it would be surprising if C had more usage than Javascript.
A lot of Rust jobs aren't "Rust jobs," that is, they don't have "Rust" in the description.
Also, "job openings" is not the same as jobs, my organization isn't currently hiring engineering roles, yet we employ ~60 engineers, of whom most write Rust. And we're just a tiny startup.
It is quite widely deployed in many non-software companies of all sizes that build custom software to run their business, and really seems to dominate in small and medium businesses.
Its also popular in Game dev mostly due to Unity, but you have other good options to use C# in game dev as well. I can't think of another kind of software company that uses it a lot other than ones that integrate deeply into the microsoft stack.
As a language, it was far, far ahead of Java for many years (the long dark tea-time of Java 7).
It is IMO better than Java these days, although they're pretty close. Whenever I have to go back to Java I find odd things missing, like unsigned long.
Sure. For me the best thing about dotnet is that you most likely find an official solution to most "basic" things needed to develop microservices (I intentionally call these basic because you don't want to worry about lots of things at this level). Go on the other hand excels at cross-cut and platform development.
Java lacks huge low-level feature set C# comes with.
It is also generally less nice to work with - Maven and Gradle are a way bigger PITA than .NET CLI (which is similar to Cargo and Go CLI), NuGet and MSBuild. Base C# syntax lends itself to more streamlined expression of business logic (e.g. with pattern matching, tuples, records and their deconstruction).
In Java, there are odd issues and resulting method gymnastics caused by generics with type erasure, and many of its base containers don't unify nicely as the ones in C# do to IEnumerable<T> or Span<T>.
Writing highly concurrent + parallelized code is way more cumbersome (and generally less efficient) with the current rendition of virtual threads, completable futures or even upcoming structured concurrency API than doing so with .NET 'Task<T>'s and their composition.
ASP.NET Core is much faster and more focused than Spring Boot. EF Core is way more powerful and significantly terser to use than Hibernate, JPA or, to an extent, JOOQ.
You can also relatively easily ship fully self-contained and relatively compact (with trimming) applications, often as a single file. With additional effort, NativeAOT provides native compilation and smaller-than-Go binaries while having much wider support across ecosystem than GraalVM Native Image within JVM space (e.g. it's one command away to get a gRPC-based ASP.NET Core microservice template which compiles to fully native binary, it also does not use any special tricks - just regular code).
They are very much not 1:1 languages. Depending on the domain, there may very large differences in developer productivity, level of comfort and effort required to achieve a competitive implementation.
Java strengths lie in its comparatively larger and more diverse ecosystem in enterprise space alongside certain high-profile projects, predominantly by Apache foundation. On technical merits it does have less to offer.
The main technical exception - Java has superior GC implementation(s).
I'd say if your goal to expand your horizons, then it's more important to pick a problem that can't be nicely solved with Java. In that case, C# will offer more pleasant and moderately familiar experience over C, C++ or, to an extent, Rust (which is another great language to learn).
Scala and Kotlin are an option on JVM, Aspire is basically catching up to Spring Boot, Quarkus, Micronaut, and Maddy Montaquila is on the record they are trying to use Aspire as means to bring developers into .NET, given the current adoption hurdles outside Microsoft shops.
Yes JVM currently sucks in value types and low level C++ like coding, .NET is great there, but not everyone needs those capabilities, and when they do, most don't shy away of doing some JNI.
On the technical level, .NET doesn't have GraalVM like tooling (it is a whole compiler framework not a plain AOT compiler), the MSR Phoenix project was canceled, Longhorn was canceled so nothing Android like, no real time GCs and bare metal deployments like PTC, Aicas and microEJ, no VMs for M2M, copiers, telephone switches.
In what concerns EF, I keep my point of view that I rather use Dapper with SP.
I definitely liked writing Kotlin code in the past, but we had a medium sized Kotlin web api at a previous job, and a very large c++ app. The c++ app was quicker to compile than the Kotlin app on many many occasions, and the toolchain and IDE integration situation reminded me (not in a good way) of working with eclipse - even with intellij
I am not a big fan of Kotlin, given its community specially in Android circles, but the language is there as better Java.
Compile times might be an issue, I know it was an issue in the past.
C++ can actually be quite fast to compile, if the right decisions how to approach the build infrastructure and code styles were taken, which usually is not the case, hence its fame to slowness.
I don't think there's anything you "can't" implement that the language reserves for itself anymore. There may be some edge case or something but in general it's no longer an issue.
The language reserves syntax for itself; you can't overload a[b] to mean anything other than either array/slice indexing or indexing into a map. You have to write methods like ".Get" or something, there's no __getattr__ or anything.
Go has good (enough) built-in arrays and maps. But if you want a tree, without generics, you're really limited. With generics, you can get a nice containerized type, but you don't get the nicer syntax that the builtins have.
It's not just types, either. Look at the signature for the built-in sort, which is amazingly cumbersome to use. A generic wrapper around it hides all the ugly.
It might just be user error on my part but it wasn’t entirely clear to me:
1. How it should be implemented “correctly”
2. The resulting code isn’t clear how it works at first glance (particularly with the yield command, it has “magical” properties that take a little effort to grok)
I did write this at something like 11pm so it’s entirely possible I’ve done this completely wrong though.
Also please ignore the weird use of mutexes here too.
I’m also aware that sync.Map could/should have been used here. This struct was more of an experiment than anything that will ultimately find its way into production code.
Mid-tier engineers are the most dangerous type of engineer as they've learned enough to make decent abstractions and they tend to run with that over-engineering everything they touch.
IMO it is better said that Go is designed as being a good language for Senior and Junior developers, where mid-tiers will probably hate it.
In the end it was a dumb comment by one of the Go devs which got jumped on by all the Go haters. Contrary to the popular meme, Go's not just for mid-tier programmers - it never was (and to be fair to Rob Pike, they've twisted what he said). But sure, it makes it easier for programmers at all levels to get started, and to get real work done. That includes advanced people as well as the inexperienced.
I think the ultimate goal of making a programming language is to cause the least friction for a programmer trying to get real work done, and in my experience Go's great from that point of view. Language bells and whistles may be exciting, but often don't pay their way in terms of real world productivity, IMHO.
I have never understood why go fans think “make it easier for beginners” is an important feature. I mean, sure, it’s nice to have, but not at the expense of anything else — your career lasts 40 years; why optimize for the first month experience?
Pretty much every reputable computer scientist has been saying for the last 70 years that the most important thing in the entire world is simplicity and ease of understanding.
I like that meme graph with the junior programmer liking the simplicity, then the mid level programmer liking the power, then the senior going back to liking the simplicity.
> I have never understood why go fans think “make it easier for beginners” is an important feature.
Well, in the real world a lot of people have to work on teams where many of their co-workers never grow beyond beginner level. So anything that can be done to reduce the burden of having to deal with them is welcome. Not everyone gets to sit in the Silicon Valley ivory tower beside the greats.
> your career lasts 40 years
40 years is a peculiar number. If it is your passion, you should easily be able to see 60-70 years (assuming you live to an average age), and if you are only in it for the paycheque the comparatively high salary offers you retirement long before 40 years comes around.
In the time you get everyone on the team to agree whether you should use Maven or Gradle, which testing framework to use, or figure out how to autoformat your code, your Go program will be done.
* everyone agrees to use `cargo test` (what even is a “testing framework”)?
* everyone agrees to use `cargo fmt`
What’s the advantage of go here?
By the way, the formatting situation is actually worse in Go because there are both gofmt and gofumpt used in the wild, at least gofmt has different behavior depending on different flags, and there are additional linters people use to e.g. ban long lines that for some reason the formatters don’t cover.
It may have been a dumb comment, but there's some truth to it. Go was to solve a Google problem of devs that needed to write system management programs, not systems themselves. The choices of Python or C/C++ left a large gap that Go filled.
I used Go for most of my own projects and as I got deeper into it began to realize its warts, but the worst was that you can't get performance by "share memory with communicating"--channels are slow. Reading the non-idiomatic stdlib implementation shows the difference of who it's made by vs who it's for (which isn't the authors).
> Go was to solve a Google problem of devs that needed to write system management programs, not systems themselves.
What's the difference? The opposite, so to speak, of system is script, and I don't think system management falls into the scripting category. A system management system is a system too. But that isn't what they were talking about anyway. They were talking in the context of building servers (think like a HTTP server). That was clearly spelt out.
I understand that the Rust crowd has reimagined system to mean something akin to kernel, much like they have reimagined enums to be akin to sum types. Taking established words and coming up with entirely new meanings for them is what they like to do. But that reimagining has no applicability outside of their little community. This is not how the industry in general considers it.
It’s funny you mention that: my on and off years with go have locked a couple (possibly wrong today) ‘rules’ for go; one is that channels are crazy slow, and another is that defer does not always work like you imagine it will.
There was a sort of misunderstood dream in the early days of go that it would make fanning out and using your 24 cores easy as empowered by channels: this is still not easy in go, although it may be easier and less error prone than c.
In the intervening decade, python has made say a parallel for loop immensely easier.
Not sure simplicity of spec translates into simplicity of user code. This change is backward compatible and will allow things that previously were disallowed.
Anyway, I like seeing this slight reversion in favor of simplicity, I think it's the right call for where Go's targeted: being a better Java for teams of mid-tier engineers.