Hacker News new | past | comments | ask | show | jobs | submit login
How generics are implemented in Go 1.18 (github.com/golang)
288 points by komuW on March 1, 2022 | hide | past | favorite | 215 comments



So maybe 7-8 years ago, I got into a bunch of arguments with people on Hacker News because I said that Go's type system was ineffective without generics. At that time, Gophers leaped to defend it, going so far as to say that it's better because it's simple. Oh and it was really important to Gophers that Go compiles in a single pass (which I argued is only relevant for truly enormous codebases like Google's).

A few years later we had go generate. Which I argued was a glorified C macro system to get around the lack of generics. Oh and everybody forgot about the single-pass thing, I guess.

Now in this thread there are a bunch of people commenting that they can't wait to combine all their copy pasted code using a generics which are implemented via a tree traversal that actually kind of complicated the idea of what a pass even is (I think memoization could make this linear, but at the cost of memory?).

This is a language that seemingly insists on breaking the wheel. Notice I didn't say reinventing, because in over a decade they haven't actually gotten a working version of the wheel out yet. I mean seriously, Go was released in 2009: if I'm not mistaken, C# had a pretty good generic system already, and there were a bunch of other workable options to copy from less-mainstream languages. Not that copying has helped: they copied coroutines from other languages, but failed to present a coherent memory sharing model (just copy Erlang FFS), which severely limits the usefulness of coroutines.

Every few years someone gets me to try Go out again and I discover, yet again, that it's still struggling with problems that were solved before it existed. It's a shame that this language has stolen so much mind share from more deserving languages.


> Every few years someone gets me to try Go out again and I discover, yet again, that it's still struggling with problems that were solved before it existed. It's a shame that this language has stolen so much mind share from more deserving languages.

Go steals the mindshare because it focuses on the important problems that other languages neglect in part or in full: performance, tooling, ecosystem, simplicity, readability, learning curve, etc. Much important software has been built in fully dynamic languages (never mind a 99% type-safe language like Go), but it’s a lot harder to build in a language that lacks important libraries or is prohibitively slow or for whom it is hard to find/train developers, or whose tooling is poor, or which is difficult to read/maintain, or etc.

So yes, Go is now looking at generics because it’s tackled the more important problems. Not because it was the most important problem and the developers were just too stupid to understand.


I bet that if the Docker and Kubernetes ecosystem was born today, Rust would be used instead.

We are already seeing the transition happening, e.g. Deis Labs:

https://deislabs.io/posts/still-rusting-one-year-later/

> Given the team’s background in many Go projects, we often hear something like this: “Well, what about Go? Do you regret moving to Rust? What do you miss from Go?” Addressing this in the context of our discussion of Rust felt like a smart decision.


You clearly have no idea what Rust is for, or who the target audience is. Application developers do not need a non-GCed language, and anyone who argues for the added complexity and noise (because Rust makes it so easy you'll say) is actually just looking for something cool to learn and push at the office. I get it, you don't want to write Go, it's too simple for the 1%er programmers. I've been a kernel developer for several decades now and it's time for something like Rust to come along, and despite the fact that I wish it had a stable ABI and that it doesn't solve the problem of C's simplistic ABI or the code that will continue to be written in C, I hope it succeeds in making things at least a little bit better for driver authors. But that's what Rust is for, it's a systems programming language.


You clearly don't have any idea who I am, or my opinions on Rust.

Since I feel like educating you,

> Nothing really, Rust's killer application are domains where using a language with automatic memory management and support for value types isn't an option, trying to use it elsewhere, while possible is sacrificing productivity.

https://news.ycombinator.com/item?id=30391431

That doesn't change the fact that many relevant projects in CNCF are now adopting Rust in their reboots.

Deis Labs, which you also don't know who they are.

Linkerd2 proxy was written in Rust, despite Linkerd 2 being written in Go.

The famous rewrite from Go into Rust done by Discord for their microservices.

The Amazon Bottlerocket uses Rust for large part of the user space, as does Fuchsia nowadays.

Any language can be a systems programming language, even Go, as ARM and F-Secure do with their bare metal Go deployments for microcontrollers.

Go's future is assured, no need to panic.


>You clearly don't have any idea who I am

Why would anyone here have any idea who you are? Your username is not informative, and you don't say who you are in your profile. That's perfectly fine, of course, but you can't then expect people to recognize you as being a particular individual.


I don't hide behind nicks, my online life is relatively easy to find, and besides the tone should have started in a different way than "You clearly have no idea what Rust is for".


Well you still haven't responded to my other comment, your profile says your primary stack is Java, and a quick google revealed nothing about you the than the fact you seen to spend more time on hacker news writing comments than you do building things, so actually I was right to take your opinion on a deeply technical topic that is quite out of your wheelhouse worth a grain of salt.


Whatever dude, you can't even read the profile properly.


You fail to address the core point of the parent: a no-GC language like Rust does not compete in the same space as Go.

You either need the low overhead of no-GC or you don't. If you don't, choosing Rust is a mistake.

---

As for your examples, I'm familiar with the Discord rewrite and I looked up the Linkerd2 proxy. Both programs are low-level services that are designed to handle as much load as possible, so yeah they should be written in a non-GC language.

---

> Linkerd2 proxy was written in Rust, despite Linkerd 2 being written in Go.

From [2]:

"The decision to use Rust came down to several factors. First, a service mesh proxy has some pretty stringent requirements: because it’s deployed as a sidecar on a per-pod basis, it has to have as small a memory and CPU footprint as possible. Because most or all of the application’s network traffic flows through the proxy, it needs to have minimal latency overhead, especially worst-case tail latency."

This should obviously be written in a non-GC'd language.

---

> The famous rewrite from Go into Rust done by Discord for their microservices.

> microservice**s**

Nope, just one microservice; at least according to the article ([1]).

From the article:

"In order to get quick atomic counter updates, each Read States server has a Least Recently Used (LRU) cache of Read States. There are millions of Users in each cache. There are tens of millions of Read States in each cache. There are hundreds of thousands of cache updates per second."

So again, the same exact story - stringent latency/speed requirements.

---

[1]: https://blog.discord.com/why-discord-is-switching-from-go-to...

[2]: https://linkerd.io/2020/07/23/under-the-hood-of-linkerds-sta...


The languages compete on whatever people decide to use them for.

People also use JavaScript, Python and Ruby for use cases that ask for AOT compiled languages

Thankfully no one is going to jail for using a language where it doesn't belong.


I'm not worried about Go in the slightest, and based on your other comment it sounds like we share somewhat of the same opinion on overuse of Rust so I will apologize for jumping the gun. But most of these userland uses of Rust are stupid and the parts people believe they need Rust for will be eaten up by eBPF anyway. Kernel development you still need a stable ABI, and while I already said I think Rust makes sense for drivers/FS (using bypass or as a module) or system internals it's not yet a good language for a full operating system unless you're willing to write the entire distribution in Rust. People still have a bad taste in their mouth for GC and I get it. But these aren't the early years of Java. Google has some of the best mm minds money can buy and most people aren't going to outplay them at their own game. I would also argue that using Go for systems programming is also stupid. Setting up freestanding Go is a PITA and again, no stable ABI, nothing in the language that makes it particularly better than long standing systems languages or a language that is actually innovating in the area (Rust). People say pick the right tool for the job but that's just salesman talk for use the tool that seems the coolest that management will let us use.


Please don't use Discord as an example. They are proprietary and quite hostile to its users through its data and metadata hoarding practices.


Plenty of FOSS projects don't share your opinion on where to host their forums.


Of course there’s no way to know for sure, but I would be more inclined to use Rust for projects where the domain is well-understood; however, Kubernetes was (and to an extent, still is) experimental and rapidly evolving. It would be painful to have to do all of that iteration in the presence of a borrow-checker. And yeah, I understand the purported benefits of having a robust type checker protecting you in a refactor.

That said, Kubernetes isn’t a monolith so it doesn’t need to be all-or-nothing, and one component that would likely be well-suited to Rust is etcd.


Naturally that was an what-if, my comment was based on what we see happening with CNCF projects evolution.


There is absolutely nothing mentioned in your comment that Java or C# didn’t already do better 10 years ago, let alone now. The only somewhat redeeming quality of go is virtual threads, but that is not even the reason like 90% of its user base uses it for.


AOT compiler; single binary; standard library and package ecosystem well suited for a subset of programming tasks, while being much lightweight and less verbose than Java / C#.

Would not pick Go to make a website. But it's probably best tool to make a simple HTTP server or command line tool that deals with files or network.


Java has AOT compilers since 2000, they just weren't free beer.

.NET was released with AOT compiler, although it only did dynamic linking (NGEN).

Mono always supported AOT compilation, just like the C# dialects for Singularity, Midori, and .NET Native for Windows 10 UWP.


> Java has AOT compilers since 2000, they just weren't free beer.

"just"

Tell me how me, a student in India with a cheap plastic laptop running Linux, could get straightforward access to some AOT compiler produced by some commercial company in north pole or somewhere.

Open source compilers got popular for a reason.

Now we have graalvm, but getting something running with graalvm is not always straightforward. Even with a full framework like quarkus I have had to use escape hatches @RegisterForReflection. Usability matters. And Go was built from day 1 with AOT in mind.

And lastly Go has better built-in / first-party supported APIs for file I/O, networking etc.. Subjective thing, I guess. When I want to make a database backed website, I reach for spring boot or vert.x; But Go wins for CLI stuff.

I don't know much about C# because I primarily work on Linux.


Like always, on a street bazaar, or are you going to tell us they don't exist?


I don't think some obscure company's compiler for Java would be being sold on streets of Bangalore, much less likely in affordable price, much less likely in smaller towns.

And, to reiterate, usability matters, defaults matter. Cannot compare 'there existed some obscure vendor who sold an obscure feature in an obscure corner of Europe or Canada', with a mainstream programming language sponsored by a big tech having a good-enough default toolchain adopted by many companies and has lot of jobs;


My experience, the catalogues used to be quite big and it was only a matter of time to get whatever.

Then again, maybe things changed in 20 years.

The technology for doing JIT caches on OpenJDK comes from J/Rockit, while OpenJ9 comes from IBM Websphere.

From 2000 to 2009, Red-Hat used to have an Eclipse version compiled with gjc.

Hardly obscure vendors.


> Java has AOT compilers since 2000, they just weren't free beer.

Commercial offerings are quite obviously not comparable to free ones.

Seems like Java can't really do AOT, even in 2022: https://news.ycombinator.com/item?id=30444454

Now, I say "really", because this is not about whether you can technically compile Java AOT or not. It's about the level of support - in Go AOT compilation is first-class. In Java, you're stuck looking through multiple second party options, some paid, some with caveats, bugs, etc.

First-class vs second-class support makes a massive difference.

As an example, this is like seeing "Go's advantage over java is its autoformatter - gofmt" and responding "Well, Java has autoformatters too!". Technically true, but irrelevant - in the real world, gofmt is used on 90%+ Go projects. The few Java projects that do use autoformatting each use a different formatter with different sets of rules. First-class vs second-class.


Actually, there used to be GCJ which was a free AOT compiler for Java


GCJ had compatibility issues. It never supported Java 1.5. It also couldn’t handle static linking properly. For a large-scale project it usually wasn’t an option.


If someone cut out a bunch of features from Java and .Net (classes, inheritance, exceptions, nominally subtypes interfaces, etc), added value types (in the case of Java), simplified the build and dependency management systems, static (and ideally native) compilation by default, etc then I would be very interested.


It's not an either/or though. How many and what companies use Java / C# for new applications these days? They're a solid working horse, and lowkey Java is my retirement plan.


Not only that, only .NET has tooling comparable to stuff like JFR.


Sorry, but I just don't buy it.

Performance just isn't the problem people say it is. I've done a lot of Python development over the last 12 years, and in that time, I've run into lots of performance problems, but none that would have been easier to solve in Go and none that were particularly hard to solve in Python. In most cases you're just offloading onto libraries (i.e. Pandas or MPT). I've dipped into C a handful of times, but that was more of a "could" than a "had to".

I'll give you a point on tooling--Go's tooling is pretty good. There are a bunch of languages with good tooling these days, but compiling easily to a single binary blob is still a pretty winning feature. But usually that sort of stuff is automated out in the first week of development anyway--is that really enough to justify choosing a language?

Ecosystem? I'm not impressed. There are like 10 languages with healthier, more active ecosystems.

"Simplicity" is generally a pretty bad argument, because what's simple is so subjective. My observation is that simple problems are simple in Go, which makes it a great language for demos, but if you actually try to solve complex problems with it, the language completely lacks features with sufficient abstraction to make complex problems simpler. Where other languages you end up spending 20% of time solving 80% of problems, and 80% of time solving 20% of problems, with Go it's more like you spend slightly less, 15% of time solving 80% of problems because of the simplicity, and then you spend 300% of time solving the other 20% because the actual hard stuff is much harder in Go.

Readability: this sort of goes back to the simplicity thing. Sure, go has less boilerplate than some languages, but that only matters for the simple stuff. If you get into, say, a complex networking algorithm, Go code is going to get lost in all the nonsense of making sure threads share memory properly. I won't argue that, for example, Erlang's syntax isn't a bit ugly, but I can explain my networking code in Erlang to someone who doesn't know Erlang fairly easily because of message passing.

Learning curve: beating a dead horse here, but just because it's easier to learn to solve simple problems in go, doesn't mean it's easier to learn how to solve professional-level problems with go, let alone industry-leading problems which actually constitute the competitive advantage of a business.

Go steals the mindshare because people saw Google were using it and mistakenly thought that if Google were using it, it must be good, even though almost nobody has the same problems as Google.

> So yes, Go is now looking at generics because it’s tackled the more important problems. Not because it was the most important problem and the developers were just too stupid to understand.

Just to be clear, I didn't call anyone stupid: on the contrary, I think the developers fell into a trap that is particularly dangerous to smart people: we think that because we're smart, we already know the best way to do things. That's not stupid, it's ignorant. I don't mean that to be insulting although I'm sure you'll take it that way, but I'll say that ignorance is fixable.

It's telling that in your post you just spit out a bunch of buzzwords without any comparison to any other languages, which is pretty much how Go got here: Go developers generally only think Go is great because they've never used other decent languages. If you're coming from C, then you can be forgiven for thinking Go is the bees knees, but that's comparing a 50 year old programming language with a 10 year old one--C has lots of problems, but there are historical reasons for those problems. Go has problems that Gophers don't even know are problems, because they've never spent enough time working in a language that actually solves those problems. And there's no excuse for it, because Go has no historical reason for not solving those problems: a lot of them were solved by other languages before Go existed, and the developers were either ignorant of the existing solutions or thought they knew better (and definitely didn't, in my experience).


> Performance just isn't the problem people say it is. I've done a lot of Python development over the last 12 years, and in that time, I've run into lots of performance problems, but none that would have been easier to solve in Go and none that were particularly hard to solve in Python. In most cases you're just offloading onto libraries (i.e. Pandas or MPT). I've dipped into C a handful of times, but that was more of a "could" than a "had to".

I strongly disagree. I've been doing professional Python development for the last ~15 years, and there are some cases where you can call out to C/Pandas or multiprocessing or something, but overwhelmingly you end up spending more time marshaling to C data structures or pickling than you save. Specifically, I've had to port services to use a Spark cluster to offload the processing because even Pandas was inadequate (80th percentile requests would take more than 60s for a CPU intensive task, and they were also blocking the async event loop). This just isn't a problem in Go because (1) single threaded execution is often 100x faster than in Python and (2) parallelization is trivial and (3) you can easily optimize Go by moving allocations out of a tight-loop (no such thing in Python).

> But usually that sort of stuff is automated out in the first week of development anyway--is that really enough to justify choosing a language?

No, I don't think this is a make-it-or-break-it feature, but it's nice. Moreover, there's a lot to good tooling besides static binaries--reproducible dependency management, no need to set up CI to build/publish packages and docs, build systems that don't require scripting or extensive configuration (any Go developer can jump into almost any Go project), dead simple cross-compilation, standard formatters, etc.

> Ecosystem? I'm not impressed. There are like 10 languages with healthier, more active ecosystems.

The way that I think about it is that you're either in the top 5 languages or you're not; it doesn't matter much where you rank in that tier because once you're in that tier you're pretty much guaranteed to get first-class support for APIs. For example, Kubernetes, Docker, AWS, GCP, etc are pretty likely to support API clients in JS, Python, and Go. Java would probably be next and perhaps C# eventually. For the foreseeable future, Rust will probably have to rely on the community to maintain these kinds of packages. The salient point is that Go is in the top tier of languages which get first-class support, not that its ecosystem is head and shoulders above everyone else.

> "Simplicity" is generally a pretty bad argument, because what's simple is so subjective. My observation is that simple problems are simple in Go, which makes it a great language for demos, but if you actually try to solve complex problems with it, the language completely lacks features with sufficient abstraction to make complex problems simpler.

I agree that "simplicity" is subjective, but I disagree that Go is only well-suited for demos. Solving complex problems is pretty simple with Go; however, Go doesn't do well for eliminating boilerplate (local repetition) which is fine because boilerplate isn't where your bugs come from and in most cases trying to DRY up boilerplate leads to code which is much harder to understand (e.g., while a single map()/filter()/fold()/etc is pretty easy to understand, long chains get convoluted fast while a single for loop remains pretty easy to grok (even in Python, we tend to frown on complex list comprehensions in favor of nested for loops). The real value add for Go's kind of simplicity is that anyone can jump into a Go project and write basically the same kind of code that anyone else would write--there isn't much room for personal flourishes, everything is bog standard. Go is bad for "code as art" or flexing your cleverness, but it's great for developing software.

> Readability: this sort of goes back to the simplicity thing. Sure, go has less boilerplate than some languages, but that only matters for the simple stuff.

Per my previous paragraph, Go's readability isn't in reducing boilerplate (Go is famously criticized for failing to reduce boilerplate) but because everyone writes the same code and the code is fairly simple (e.g., no "inheritance vs composition" debates). Further, the ubiquity of gofmt is also pretty amazing. Some languages are catching up with the "standard formatter" idea (Python, Rust, etc), which is great, but nowhere is it as ubiquitous as in Go.

> Learning curve: beating a dead horse here, but just because it's easier to learn to solve simple problems in go, doesn't mean it's easier to learn how to solve professional-level problems with go, let alone industry-leading problems which actually constitute the competitive advantage of a business.

It's much easier to onboard developers to Go than to Java or C++ or even Python. Being able to bring on a new developer without spending days configuring their developer environment, etc is pretty great. Open source projects can attract contributors immediately and businesses don't need to restrict their hiring pool to people with previous Go experience.

> Go steals the mindshare because people saw Google were using it and mistakenly thought that if Google were using it, it must be good, even though almost nobody has the same problems as Google.

This is lazy argumentation, and trivially disproven. If this were the case, people would give up on Go and return to whatever language they came from. Instead, people who use Go for real projects tend to enjoy it or at least have more thoughtful criticisms than "Go is only popular because of Google".

> Just to be clear, I didn't call anyone stupid: on the contrary, I think the developers fell into a trap that is particularly dangerous to smart people: we think that because we're smart, we already know the best way to do things. That's not stupid, it's ignorant. I don't mean that to be insulting although I'm sure you'll take it that way, but I'll say that ignorance is fixable.

I agree, but I think this applies more to Go's critics than to Go proponents. Notably, Go proponents overwhelmingly have experience with other languages, while Go's critics fixate on how Go doesn't let them program in the idioms of their previous language. The people who try Go for more than a few weeks tend to be able to levy more thoughtful criticisms than "Go is stuck in the 1970s"--they can talk about the advantages of standardization even if they still prefer more expressive power, for example.

> It's telling that in your post you just spit out a bunch of buzzwords without any comparison to any other languages

Yes, it tells you that I was commenting from a phone onto an Internet forum and didn't want to make a huge comparison chart in ascii. :) I'm happy to debate the tradeoffs of Go relative to other languages, but it wasn't necessary to make my points.

> which is pretty much how Go got here: Go developers generally only think Go is great because they've never used other decent languages.

This is another lazy criticism. Overwhelmingly Go developers have previous experience in other languages (per the annual Go survey results). In my case in particular, I've used Java, C#, C++, C, and Python professionally and I've dabbled in other languages (e.g., Rust, OCaml, Haskell, etc) on the side. I regularly have thoughtful and articulate debates about the tradeoffs of Go relative to the aforementioned languages, and many of the other Go proponents who post here do as well. Further, I hope you wouldn't criticize Go developers for lacking experience with other languages without yourself having extensive experience with Go.


[flagged]


I gave a reasoned argument and you responded with ad hominem. I don’t think it’s the “Go fans” who are toxic. We merely disagree, and that’s okay.


I‘m sure there are better reasons for people liking Go other than what you described. This reads like a general dismissal.


Nope, as a language Go adds nothing and sometimes feels like it gives even less.

Where Go has been a success is tooling. The tooling is pretty decent and helps a lot. But that could be added to pretty much any language ecosystem


Go apologist here. Go doesn't work because it adds something to existing languages, but because it keeps things away.

In 30 years time, Go codebases built today will still be around, while Java, Javascript, Scala or Ruby codebases will have been rebuilt multiple times over or removed entirely, because they drowned in their own complexity and their developers' own cleverness.

That said, for me it's not so much Go itself, but the mindset that comes with it of writing dumb and verbose but obvious code. I've reduced 15-line for loops into clever functional oneliners, but maybe all I needed was a `range` instead of a `for i = 0` style loop all along, because I need a lot more time and mental energy to understand the functional oneliner than a `for x in range` loop.


That’s a bit bold coming from a relatively young language, given that Java has codebases close to 25 years old.

Also, repeating the same code many times due to lack of expressiveness is not the epitome of long-lived software. Essential complexity is non-reducable, the only way we can deal with it is through abstractions.


Some of us were around when Java was young and can compare a 5yo Go codebase with a 5yo Java codebase :)

> Also, repeating the same code many times due to lack of expressiveness is not the epitome of long-lived software. Essential complexity is non-reducable, the only way we can deal with it is through abstractions

Repetition is not a big deal in a local context (e.g., error handling or for loops), and Go is rarely repetitive in larger contexts. Gratuitous abstraction is a much greater danger than extra keystrokes IMHO.


> But that could be added to pretty much any language ecosystem

Though it annoyingly is not. The profiling with integrated stack, line, and memory / alloc profiler seems especially nice.

Though it’s made progress (especially with the rise of flamegraph-type visualisation) the ecosystems I navigate seem to remain a hodge-podge of disparate tools.


I don't hate go for the language itself. I understand the point of using go, especially if you have a company with hundreds or thousands of software engineer. It is really good for building microservices, because to me the learning curve is not steep hence everybody who just learn it, could already make contributions to the codebase.

what I don't understand are the fans who are really trying to overglorify go as "language of the future" that could be a C replacement.


But the fact remains that it's a productive language with excellent tooling and fast build times, and a lot of people really like working with it (myself included).

I believe the reason it has gotten as far as it has without generics is because it did always have them -- but only for the built-in data types (slices, maps, channels). You just couldn't add your own until Go 1.18. I have several medium-sized Go projects that benefit hugely from generic slices and maps, but wouldn't benefit at all from user-defined generics.


That's the main use case I think; certain specialized container data types (e.g. a linked list) will benefit from generics, but I don't see it going as crazy as e.g. Java in terms of usage.

Which is something the Go team is behind as well; they added it to the language, but they aren't telling people how to use it, and are letting the community come up with use cases over time before they iterate on it.


Well let perfect not be the enemy of the good. The reality is that Go is wildly successful in practice for good reasons that have been hashed numerous times. As a result tons of Cloud software is written in Go.

There is nothing Go needs to be ashamed on. Its mindshare is a result of a good choice people made at a certain point in time. A better language will win at its own merits.


I'm not letting perfect be the enemy of good, I'm saying Go isn't good. No language is perfect, but there's literally not a single use case I'd choose Go for, because there are a dozen languages that do almost everything better.

> Its mindshare is a result of a good choice people made at a certain point in time.

Its mindshare is a result of Google using it, and people mistakenly thinking that if Google uses it, it must be good.


That's OK. But saying it isn't good as a fact is a bit misleading. If it wasn't any good, it would not be used and would've faded to obscurity a long time ago. That is not the case, so there must be some redeeming qualities to is


> Well let perfect not be the enemy of the good.

When you have half a dozen generic builtins but reject the idea that builtins are necessary, and can’t even be arsed to provide a working vector type, you’re nowhere near “good”.


They never rejected it. In the very first post launching it they commented that they considered Generics but didn't add them because they didn't find a way that works well with Go's principles. I'm happy it took them the time it did to get to the current design. They did have a vector type before version 1 too, they intentionally removed it for similar reasons.


Generics were considered at the very release. Go just focuses on getting it right. And I love it for it, because to this day Go is the only language that got OOP right, and now Generics.

Go is a language made for the human factor.

Go generate by the way is mostly NOT used for generics. Check for example Vugu, which compiles UI documents to pure Go code. Go generate is made to incorporate any random external tool without making it complicated for the humans using it. And it makes sense because the goal was not single pass compilation, it was single pass parsing, which is exactly WHY Go generate is useful. It is still true to the exact way this has been defined. Go is simple in ways that matter.


Where does this cause problems for you in practice? Where is the usefulness of goroutines limited practically? Where is the complex notion of a compiler pass causing problems?

I’m very open to the idea that I’m suffering from a problem I don’t know I have, but right now you’re just asserting that problems exist without actually telling us how to recognize them.


I'm not saying a compiler pass is a problem: I'm pointing out that Go fanboys originally said that they needed a single compiler pass, and that was why Go couldn't have generics. But Go has abandoned single pass a long time ago, and Go still doesn't have generics, and when it does have generics, it will have an abstraction so complex that it makes adding another compiler pass look simple.

If anything, I think compiler time is worth a lot less than developer time for the vast majority of projects, and if a compiler pass saves developers even a little time, I say add the compiler pass (within reason--don't keep doing this until the compiler grinds to a halt). This is fairly obvious and encoded into the compilers of almost every compiled programming language. But Go is learning this 12 years into its existence when everyone else knew this before Go existed.

As for goroutines being limited: here's a practical example:

1. Write a basic two-person p2p chat in Go and some other appropriate language. At a high level, each user client has a message log, and the other user can write messages to it. Since the arrays are single-reader, single-writer, there's no real problem, just use an array: this is a nice demo of how simple Go is! You don't need all these fancy abstractions!

2. Expand the chat to include a third person. Again, everybody gets a message log, but now more than one person can write to it. Now, your message log is multiple writer, single reader (assuming each user can maintain their own log of messages they've sent so they don't need to read other people's logs to get those--there's an implicit copy here but that's fine). In Go, start over and write a probably-buggy implementation of a SRMW queue because Go is too simple to have that already. In any reasonable language, wrap writes to your message logs in a built-in queue that's already thread-safe. Note here that "any reasonable language" in this case includes Java (LOL!). In Erlang/Elixir/Clojure, you may literally have accidentally supported a third user already and have to make no changes. There are of course other go implementations which are maybe more idiomatic, but none of them approach the simplicity of a language that supports message passing.


No, the documentation stated that Go was designed for reading without backtracking. That results in single pass compilation being possible and thus being faster, but that was never a limitation that was imposed. The reading without backtracking is still valid: go generate DOESN'T work on .go files, only on any other tool that generates .go files.

About the second part, you seem to mix up goroutines and channels. In a concurrent system, you would need locks for your example, and channels fix this. Goroutines are just the representation of independent processes and can be implemented and ARE implemented differently per compiler, check e.g. how tinygo and gopherjs do this.

Your example point 2 doesn't make any sense, check fan in and fan out patterns with channels. Go supports message passing, that is what channels are. You really didn't try Go and it shows.


Your example is super helpful for focusing the discussion, so firstly, thank you for taking the time.

My immediate thoughts are:

- This is rather specific. Not all programs should be using complex thread-safe data-structures. Rather, the primitives are in place to build such things, and libraries can be imported etc.

- These more sophisticated data-structures have runtime costs that are a bit less straightforward to understand as compared to arrays. They require either (a) programmers to be aware of the edge cases or (b) a higher-level language where programmers relinquish some control over runtime performance. To be clear, I personally enjoy many such languages.

Overall, your criticism is equivalent to saying "Go is too low-level". That may well be the case for many applications, but that remains to be argued more precisely.


I think OP didn't even start using go in practice, so the question doesn't help.

But I can say that it's the same ad with natural languages. No one ever feels that their mother tongue is limiting, until they learn a new and very different language and learn a pattern/style that they cannot apply in the mother tongue.


That's fair, except that I routinely program in:

- Go

- OCaml

- Python

- Lisp

I think I have a reasonably good sense of what each language offers and what it does not. But OP goes beyond saying "Go does not offer some features that other languages offer"; he effectively says that the language causes severe problems. Where are they?


"severe" is certainly subjective, so you might disagree.

From my perspective, none of the languages you listed allows for good error handling. But that is obviously only my perspective. I would call this a "severe" issue. In fact, all languages that k I know have at least one "severe" issue.


This actually has a name in programming circles: the blub paradox.

I guess in natural langages this would be the sapir-whorf hypothesis.

Blub seems like a much stronger effect though, at least anecdotally. But that might be because natural languages are rarely compared in terms of features / capabilities, and even more rarely “ranked” along various such axis.


> Every few years someone gets me to try Go out again and I discover, yet again, that it's still struggling with problems that were solved before it existed.

It is not Go that is struggling. It is you. Does literally every language have to turn into Java?


> At that time, Gophers leaped to defend it, going so far as to say that it's better because it's simple.

I'm not sure why certain things generate such fanatical community.

Eg. I remember arguing with iOS developer in my first work that smartphones bigger than 4.7" are useful. He claimed they aren't and Apple, as company focused on best experience of user, will never ever produce bigger smartphone, because it's totally useless.


I've had a play around with Go generics, and from what I can tell it boils down to passing the type parameter down the chain, from the concrete to the general, to the point where a function needs to create a structure, and then returning that structure back up again, modifying the structure with functions and methods as you go.

Rather than creating the structure and passing it down the chain to be modified via typecasts as previously.

Pretty much everything else ends up being limited by interface types being unable to reference structure attributes directly, but requiring method signatures. So it becomes more complicated to reference all types that have an 'ID' attribute for example.

With some types I ended up with so many getter and setter functions to enable generics, that it was better to revert back to 'go generate'.

Perhaps it works better with pure container types that can take 'any'.


I’m not going to defend what random internet commenters may have said, but in my view the core team has been fairly consistent and their design decisions (to me) make sense.

The first thing to understand is that Go occupies a space lower level than Java but higher level than C. It includes things like explicit pointers and more control of memory layout.

Many of the design decisions make no sense without that context. Go can’t simply copy generics from Java or memory sharing from Erlang, since the design decisions made by those languages would introduce too much overhead in a language intended to be lower level. In a lower level language more aspects of how a program executes are specified explicitly, rather than accepting more overhead or guessing what a complex optimizer will do.

On generics, I think the team’s position has mostly been that it’s a big project and there’s been other priorities like rewriting the compiler in Go, improving the GC, etc. And that in the spectrum of trade-offs for generics systems they didn’t want to go all the way to Java or C++.

I feel like a lot of HN commenters compare Go to higher level languages and find it lacking, which may be a fair assessment for certain problems but isn’t really understanding the niche it tries to occupy.


> On generics, I think the team’s position has mostly been that it’s a big project and there’s been other priorities like rewriting the compiler in Go, improving the GC, etc. And that in the spectrum of trade-offs for generics systems they didn’t want to go all the way to Java or C++.

That was not the initial team position. Their initial position was "why would you even need generics? Please provide us a usecase". They actively denied there was a need for generics.


>Their initial position was "why would you even need generics? Please provide us a usecase"

Do you have a source for this? It doesn't match my recollection. As I recall, the Go team always said that they may add generics in the future, but also that they would also like to see compelling use cases for it. That is not an unreasonable request.


They have mentioned that you don't really need it as an explanation after they already said for a long time that they do consider it, from the start. They never strayed from that POV.


This has never been the case, and stating it as a fact is a big lie


Exactly.


How is Go lower level than C#?


I think C# might be the closest mainstream language, but I’d say explicit pointers, interior pointers, and slices as the default list type might be things that make Go programming a little lower level.

There are also other differences that would prevent using C#’s system as is - Go doesn’t have the reference/value type dichotomy or inheritance. I think they also wanted to be able to abstract across float/doubles etc (unless C# added that recently).

Anyway I like both languages and I’m not trying claim one is better than the other, just trying to explain the design space Go seems to occupy.


I'm not sure what the difference is with regards to pointers. They're both garbage collected languages with value types.

There are definitely differences in the languages, but I'm not sure that Go really had anything to figure out or invent. I don't buy that argument.


> I'm not sure what the difference is with regards to pointers.

In C# most objects are 'class' objects, which are implicitly passed by pointer, although some are 'struct' objects passed by value. In Go rather than making the decision once at the type level, the decision to pass by value or pointer is made explicitly every time an object is used.

> I'm not sure that Go really had anything to figure out or invent

If you look at the discussions for Go (which started as early as 2009 [1]) [2] [3] [4] [5] generics seems as big or a bigger project as the other improvements made to the language over the last decade. My impression is there being a broad spectrum of potential trade-offs across compile speed, execution speed, convenience, etc.

The totality of the prior art here is above my pay grade, but I can quote the Haskell people they roped in [4]:

> We believe we are the first to formalise computation of instance sets and determination of whether they are finite. The bookkeeping required to formalise monomorphisation of instances and methods is not trivial ... While the method for monomorphisation described here is specialised to Go, we expect it to be of wider interest, since similar issues arise for other languages and compilers such as C++, .Net, MLton, or Rust

I think C# occupies a fairly nice design spot also (and takes advantage of its class/struct system to get fast compiles but also performance when needed). But it's not like the C# design couldn't be improved on. As far as I know you still can't write math code that works across 32 and 64 bit floats. And array covariance is implemented in a way that isn't type-safe and relies on run-time checks and exceptions. [6]

[1] https://research.swtch.com/generic

[2] https://docs.google.com/document/d/1vrAy9gMpMoS3uaVphB32uVXX...

[3] https://go.googlesource.com/proposal/+/master/design/go2draf...

[4] https://arxiv.org/pdf/2005.11710.pdf

[5] https://go.googlesource.com/proposal/+/refs/heads/master/des...

[6] https://codeblog.jonskeet.uk/2013/06/22/array-covariance-not...


Never used unsafe and raw pointers in C#? They exist since version 1.0.

Never used ArraySegments in C#? They exist since version 2.0

Abtracting over float/doubles is called generics.


It is not the same thing at all if you care to compare the details. C# pointers come with a long list of caveats which does not apply in Go. They feel like a bolted on escape hatch rather than a real part of the language.


Moving goal posts, so now instead of not existing, they come with caveats.


We use go in an embedded Linux environment. I do C#for web stuff and it would never cross my mind to do c# in an armv7 Raspberry. Also what about cross compilation? The c# runtime is not easily available to use in embedded devices at least that i know of. With go you just set it to compile statically and set the arch target. I can deploy it as a single binary. Love it after years of maintaining cross tool chains.


Here to blow your mind,

https://www.wildernesslabs.co/


Wow impressed indeed. Especially the esp targeting. Is there some intermediate compilation from c# to c or a compiler backend was created from scratch?


One major difference that makes Golang lower level is that it compiles to machine code for the target CPU arch/OS instead of bytecode.


I think this is a red herring - compiling to native code is orthogonal to low-levelness. Haskell compiles to native code, but is exceedingly high-level. Forth is traditionally bytecoded, or threaded-coded, but is excruciatingly low-level.

Java was originally bytecoded, but at some point gained various ahead-of-time native compilers. Did it become lower level when that happened? It did not.


C# also supports AOT compilation. It's definitely not a low-level / high-level language differentiator. The term generally means the abstraction level you can reach.. I'd be tempted to define it as the distance to simple lambda-calculus in the lambda-cube


C# ahead-of-time compilation has for many years come with all sorts of caveats. Early versions requires runtime system because not everything got compiled on parts. Later versions have all sorts restrictions which do no apply for Go.

I think when C# and Java people compare with other languages they treat it as a checkbox exercise without caring about how good that feature actually is.


OK but Go carries around a runtime too?


Oh, AOT compilation, great memories. How many year of CPU work and downtime it took from exchange servers, when you wait every update to “compile” for hours, just because it is awesome. Or when you getting laptop heating and you know - it is dotnet compiles and optimising something for you, another great update. Yes, C# also supports ahead of time compatibility - it is portable for whole 20 years between windows computers. How cool is that? Another advantage is speed - calculator or photo viewer only takes 1-3 seconds to open on 5 ghz 8 core cpu. Yet another advantage is size - only 3-5 gigabytes of different version libraries in your system and you are golden for a month (next you need to install preview updates, and then just updates and thats all you good, secure and protected by Windows Defender). In all these aspects C# is clearly superior language.


By bundling a runtime. The abstraction level is pretty much the same as Java/C#/JS.


I don't really see how that's relevant to the issue of pointer types.


C# generics are delicious. Combined with the strong compiler and super good reflection features, you can do very well indeed with it.


Nah… if so was embracing C# style complexity I would rather do Swift of Rust. At least they have a clean solution for fixing the million-dollar-mistake.

My chief attraction to Go is simplicity. I can be away from Go for years and pick it up again really fast. With simplicity, you naturally need to make some sacrifices, but I think Go team has been diligent about choosing those.


> Nah… if so was embracing C# style complexity I would rather do Swift of Rust.

Just so we're clear here, you're using the word "complexity" to describe the situation of not having to copy paste or `go generate` 30 different implementations of the same class for different types because your language lacks generics.


The cycles and waves in human groups is pretty weird to witness.


[flagged]


Sometimes I wish that the Go advocates had not joined Kubernetes project and triggered the rewrite of the prototype from Java into Go.


It in fact reminds a bit of Angular. I'm expecting a similar revamp of Go at some point - it will share the same name but otherwise won't have to do anything with the original.


Considering it was written by a Turing award winner and other members of Bell Labs and Google, who not only shaped modern day computing but have been doing so for the literal 50 years you referenced, maybe you should consider that the reason that Go is popular and used in many settings is because it's good, and you're the one who is A) suffering from a bad case of Dunning–Kruger B) Have yet to learn the lessons that informed Go's feature set and philosophy. Or it could just be that everyone else is wrong. Who knows.


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


How many times is this gonna get reposted as some sort of gotcha? He's right, and having to deal with juniors or large teams with varying competence isn't unique to Google. I've met many devs who praise Java for how simple it is to parse through for people of all experience levels (once you get used to the verbosity). In fact, two of Java's founding principles was that it had to be simple and familiar. Yet I don't see anyone pretending that's a bad thing.

Go has the same goals, just a different approach. Java is too verbose, Go takes the simplicity approach too far sometimes. Neither are perfect but they're definitely useful and worth considering for many projects. The fact that it was made with juniors in mind is a plus.


> having to deal with juniors or large teams with varying competence isn't unique to Google.

It's good for "juniors" but not yourself? Lmao.

No one can praise Go without speaking in the third person.


I also couldn't help gloat a bit thinking about all these bad faith arguments I heard coming from the Go community over the years about how the lack of generics was not such a bit deal and generics sucked anyway. I especially remember one particular document where some Go devs claimed to make an "unbiased" study of the pros and cons of generics and it was ridiculously one sided (if somebody remembers what this document was I'd love to read it again, especially now that Go has generics).

That being said I'm happy to see that Go now attempts to find a tradeoff between generics and compile time, if their approach proves successful hopefully it could lead to improvements for other languages as well.


Does it help to make such vague references to bad faith arguments? If you want to point to something specific that someone said, then fine, but otherwise it seems like you're just stirring up discord.

No programming language is loved only by rhetorical geniuses. Of course you can find some people who've said some silly things about Go or generics. But so what? You can also find people who've said silly things about your favourite programming language.


> Does it help to make such vague references to bad faith arguments? If you want to point to something specific that someone said, then fine, but otherwise it seems like you're just stirring up discord.

Sorry, but anyone who's been watching Go's development long enough remembers this: it's not being made up.

Dischord is good if the alternative is consensus around a backwards language that wastes huge amounts of people's time and money.

> Of course you can find some people who've said some silly things about Go or generics. But so what? You can also find people who've said silly things about your favourite programming language.

The problem being that in the case of Go, the people saying silly things are the primary developers of the language.


Again, you're just saying that a bunch of people said some unspecified silly things. It's difficult to take that seriously as a critique of Go or any of the people involved in designing it.


I wasn't making an argument at all really, just sharing my point of view. I also didn't think that the claim that many bad faith arguments regarding the utility of generics came from the Go community would be scrutinized and needed supporting. Besides it's irrelevant and petty to bring it up now, I'm more interested to see how these new generics fare.


It is irrelevant and petty to bring it up now – but you have. I think you should either withdraw the comment or point to an example.

Making broad brush accusations of bad faith just doesn't help to get to the bottom of any technical issue. It's fine to express your opinion, but if you publicly say bad things about a community, you should be prepared to substantiate those claims when asked.

HN commentary on Go seems to repeatedly return to increasingly exaggerated and vitriolic claims about the supposed idiocy of the Go team or Go community in relation to generics. The factual foundation of these claims is virtually nil. It appears to be some kind of group false memory phenomenon, where vague second hand reports, endlessly repeated, become accepted as fact.

I would therefore invite you to actually try to find some examples of these bad faith arguments, for your own benefit if nothing else.


This approach (sharing code for various generic instances and passing in a blob of type information) is used for generics in some other languages/runtime environments - for example .NET will in many cases do code sharing like this where it will generate a reusable generic function that operates over many types and then pass it type information so it can operate properly instead of having to generate 50 different versions of a function like a C++ compiler does. This obviously can have some performance implications, but it makes sense to do it (especially in Go's case where what came before it was tons of virtual calls anyway).

In .NET on Windows you can sometimes observe this because generic types in your stack traces (in the debugger) will be replaced with 'System.__Canon' [https://twitter.com/matthewwarren/status/1161249300401311745] instead of the actual type - this indicates that the type was completely erased, so the current function could be running for any number of types and the type can't be identified based on the current instruction pointer.

The shared code + blob approach becomes more necessary in an AOT compiled environment like Go (and you'll see it used more in AOT compiled modes for .NET) since you can no longer rely on being able to on-demand JIT some optimized code when a new type shows up.


I thought this was something like traits, but it goes way beyond that.

Sub-dictionaries are described here: https://github.com/golang/proposal/blob/master/design/generi...

It looks like the compiler needs to walk down the entire call tree from the top-level generic and then compute new dictionaries for each called function. Since the compiler may not know the entire call tree, it may have to build nested dictionaries.

Wacky stuff!


From the article you linked, those subdictionaries seem to support calling a function g[T1] inside a function f[T1, T2].

There’s a concept called polymorphic recursion, supported by languages like Haskell, which goes beyond that and allows using arbitrary derived types in a function, in particular, in recursive calks.

My interpretation of the section in “non-monomorphisable functions” in the original article is that Go’s compilation strategy doesn’t handle that currently.


These "GC shapes" a lot like GHC's "runtime reps": https://hackage.haskell.org/package/base-4.16.0.0/docs/GHC-E...

GHC allows recursion that is "polymorphic in the types" but "monomorphic in the runtime reps". There is no reason why Go shouldn't allow polymorphic recursion that is "monomorphic in the gc shapes" either, though they might not have bothered to allow it yet.


I think they do; the problem is that Go puts way less things behind a pointer than Haskell does. Every struct is its own runtime representation. If you change the example in https://github.com/golang/go/issues/48018 to use a pointer indirection, I think it should work.


Hopefully Haskell will get some monomorphizing generics and we will make less stuff boxed too!


Maybe I missed it in TFA, but what runtime type information is necessary for a generic function call?


Does Go support separate compilation? The approach sounds like a change in the implementation of a generic function (changing the gcshapes) could cause client code to break unless it is recompiled as well. What am I missing?


You say "separate compilation", but what you are really asking is whether Go supports forward-compatible shared libraries.

Go does separate compilation, it's an extremely important property for Go.

Go code can be built as a shared library, loadable by both C or Go code, albeit this is an unusual use case for Go.

However, when multiple Go shared objects are to be linked together, they must share the same Go version, including the toolchain version, so there is no forward compatibility.


This is also true in C++ templates, and C macros for that matter.


There are "shared" and "c-shared" build modes[1], if that's what you mean. The latter is fairly obvious. The former is so rarely used that there was a proposal to remove it, but it turned out that some people did actually use it, but almost exclusively because of license terms.

[1]: https://pkg.go.dev/cmd/go#hdr-Build_modes


Yocto/OE used to build Go in this shared mode just to conserve space that would otherwise be wasted by each Go program shipping its own copy of the standard library. It did shave off some MBs, so hard to say it wasn't useful especially if a bunch of +20MB binary blobs are a concern.


Go is all from source


Except for plugins. But plugins are pretty darn niche, and feel more experimental than anything: https://pkg.go.dev/plugin


Note that if a plugin and the main program have any packages in common, the build-hash (Go binaries include a hash for each included package) must be identical between the program and the plugin. If the hashes don't match, then the program's `plugin.Open(…)` call will return an error.

So if there is a package in common such that an implementation change could cause something like a cgshape change, well the cgshape change won't matter since any implementation change will change the package's build-hash. And so everything will need recompiled anyway, regardless of whether the cgshape changed or not.


Interesting I hadn't caught that requirement before. It both kinda makes sense and kinda doesn't... and sadly throws some cold water on a few "it'd be fun to try to do X" ideas I've had floating around in my head.

I suppose there's always RPC-like options.


Does this even include the standard library?


Yes, it does include the standard library.


While (iirc) Go uses caching in its build system internally, it builds quickly enough that this is not a concern.


By the way, are there any projects underway to produce a library of the most common basic template functions? I'm very much looking forward to unifying some of my most copy-pasted functions when 1.18 is out, but I would like to unify on a central library right away.


It's partially being done in the x/exp module to prototype it thoroughly before including it into the stdlib.

See:

* https://pkg.go.dev/golang.org/x/exp/constraints

* https://pkg.go.dev/golang.org/x/exp/maps

* https://pkg.go.dev/golang.org/x/exp/slices


Cool. I'd love to see this become part of the stdlib rather than competing external packages.


If it becomes part of the stdlib, the update cycle will match the language. For now, improvements will come a lot faster as an external package


I believe the goal is for stdlib packages to be added with 1.19 once they have some real-world use. For now they're in x/exp to allow changes before that release.


They want to be able to break backwards compatibility, so they can experiment more. If they put it in stdlib, they cannot break it anymore.


I made a crude proposal for generics in 2011[1], in which I proposed a pair of concepts ("storage class" and "type class") that are somewhat similar to the concept of this "gcshape".

I proposed it as a compromise between full monomorphisation and runtime code generation.

[1] http://oneofmanyworlds.blogspot.com/2011/11/draft-2-of-propo...


Something I haven't been able to pull up yet: what does the addition of generics do the `reflect` package? I assume it needs to be extended to deal with reflection through generics?


All types are concrete at run time, so, no it doesn't end up needing changes: https://go.googlesource.com/proposal/+/refs/heads/master/des...

"We do not propose to change the reflect package in any way. When a type or function is instantiated, all of the type parameters will become ordinary non-generic types. The String method of a reflect.Type value of an instantiated type will return the name with the type arguments in square brackets. For example, List[int].

"It's impossible for non-generic code to refer to generic code without instantiating it, so there is no reflection information for uninstantiated generic types or functions."


Note that with this change, only "gcshapes" are static at runtime. Any other information reflection needs would have to come from the dictionary. That would bloat the dictionaries.


Do Go generics support covariance and contravariance?


Go does not have subtypes, so your question is not applicable.


It does have subtyping relationships via interface. It absolutely has subtypes in the co/contravariance.

In theory, a slice of structs that implement an interface should be able to be used as a slice of that interface. But due to the implementation, that requires a full copy.


No it's not due to the implementation, it's due to the language specification: there is no general subtyping.

You can see it this way: there is implicit syntactic sugar that creates an interface value (pointer + type info) when you assign a value of type T to a variable of type interface I where T implements I. This happens on variable assignment, function parameter bindings and return value bindings.

A slice of Is is just a very different type from a slice of Ts. The assignment magic sugar works, but it works at the level of slice elements, not the whole slice.


It’s not just a random implementation artifact. Since all slices in Go are mutable, it logically wouldn’t make sense to upcast them while keeping the reference. That would mean the ability to put other objects in the slice. Same for pointers.

That’s a basic fact of type safety that people often get wrong. For example, the JVM famously allows upcasting arrays, with very surprising results.


Java fixed the mutability issue for its generics, you can insert Cats and Dogs into a concrete List<Animal>, but you cannot insert them into a List<? extends Animal>.


Ah yes that's true it is mutable

But even an immutable list cannot be made covariant. Or, say, funcs.


> a slice of structs that implement an interface should be able to be used as a slice of that interface

It absolutely should not even if it could (which it can not since the memory layouts don’t match), as that undermines the type system.


If it were immutable at least, the type system wouldn't be undermined at all.


> If it were immutable at least

Yeah but immutability’s not even remotely a thing in go land.


Can you use generics to create a container of an interface or does it only support concrete types as generics?


I think the Haskell compiler GHC is using a similar approach (directories) to implement type classes.


When is generics officially coming into Go? I thought it would be in February as promised?


The original plan was to release in February, but in the Beta 2 release [1], they said this: "Because we are taking the time to issue a second beta, we now expect that the Go 1.18 release candidate will be issued in February, with the final Go 1.18 release in March."

It looks like there are still a few release blockers [2]. I'd imagine RC is fairly soon though.

EDIT: as mentioned by _fz_ below, RC1 has already released. Seems like the full release will most likely still be in March.

[1]: https://go.dev/blog/go1.18beta2

[2]: https://github.com/golang/go/issues?q=is%3Aopen+label%3Arele...


> I'd imagine RC is fairly soon though.

RC1 was released two weeks ago.


Well, not quite 2 weeks ago; it'll be 2 weeks on Thursday. I say this because policy is to issue a release "no sooner than two weeks after" issuing the release candidate.

https://go.dev/s/release


Thanks! Good catch! I was going off blog posts and didn't see that there was a download for RC1.


go1.18 is coming out soon with generics. The first release candidate came out 2 weeks ago. You can track the open issues here: https://github.com/golang/go/milestone/201


1.18 milestone still have a few issues left. https://github.com/golang/go/milestone/201


Disclaimer: I only very occasionally touch Go code.

Hasn't this been a very long time coming? Iirc there has been much dispute over this in the community. Can anyone weigh in on what the other options were?


In a nutshell, torn between the extreme ends of high compile-time cost and large code size of a C++ flavored implementation and the high runtime cost and dangers of boxing/unboxing (Java), Golang opted for "none of the above" and tried to limit complexity drivers / powerfulness / expressiveness / usefulness instead, keeping the impact on both compile time and run time low.

About how well that decision will turn out in practice, we'll start to know soon.


The compile time issues for C++ are entirely because of the creaky header mechanism that doesn't permit efficient parsing. Generics have worked fine in Ada since its inception. Any modern language can adopt the same process of tracking a formal abstract interface that is parsed once and expanded into concrete code as necessary.


The process of expanding genetic code into concrete code based on the types (monomorphizing) can also be an expensive process. At least, people in the rust world point to it a lot as a reason why some crates compile slowly. Monomorphizing multiplicatively increase how much code LLVM needs to process (and optimize).

Mind you, C/C++’s ridiculous header system is probably still a much bigger issue. Especially because C++ templates usually need to go in header files.


Although monomorphization takes a significant chunk of a typical Rust compilation, it pales in comparison to LLVM codegen and execution of procedural macros. So although it's academically worthy of note, optimization-wise it's not where the focus should be. Personally, I'd like to see someday a Rust compiler which is not based on LLVM. Both Zig and Jai compile much faster when using their non-LLVM backends than when using LLVM, so Rust is not the odd one here either.


> Although monomorphization takes a significant chunk of a typical Rust compilation, it pales in comparison to LLVM codegen

It's not the monomorphization itself that takes a long time, it's the LLVM codegen due to all the extra code being generated from duplicated function implementations. I've heard macros slow for largely the same reason, it's not the macro execution itself that's slow, it's compiling all the generated code.


> I've heard macros slow for largely the same reason, it's not the macro execution itself that's slow, it's compiling all the generated code.

If that were true, the same issue would apply to hygienic macros, yet it doesn't. It's distinctly a problem with proc macros.


> Mind you, C/C++’s ridiculous header system is probably still a much bigger issue. Especially because C++ templates usually need to go in header files.

IDK about that. A project I'm working on is template-heavy in a major way, entirely header-only (~15kloc of this: https://github.com/celtera/avendish/blob/main/include/avnd/c... more or less) but with a barely correctly set-up dev environment with clang and PCH (a couple lines in CMake), ninja and mold, individual rebuilds are pretty much instant ; a complete rebuild which on my machine builds 49 libraries and 38 executables takes a whopping 7 seconds, CMake included.


Increases compared to what? If you don't use generics and write the code for each type manually, you'll end up with exactly the same thing.


I often wonder about that - if I was going to write by hand I might end up extracting a simple interface, do some type coercion/promotion or some other trick to reduce the number of functions I need to write.

I read about how .Net does Generics at the VM level and was very impressed, a good compromise between C++ "macro expansion" and Java "type erasure", both of which seem extreme.

At the end of the day, though, when generics are present I stop worrying and just use List<int>, List<float>, List<char>, etc. without a second thought. The compiler, VM or runtime can deal with it although I might get punished in some way.


Increased compile time compared to doing polymorphism with an extra layer of indirection, I’m guessing


Isn't that solved with c++ modules [1]?

[1] https://en.cppreference.com/w/cpp/language/modules


Not really, in C++ templates still need to be instantiated in every module but modules don't have to each repeat the parsing of template definitions. The module that declares the template will do the job of parsing the template and producing some intermediate representation of it that other modules can consume. Okay that does save some amount of time but the overwhelming majority of time spent compiling templates is in instantiating them, not parsing them from literal text into an AST.

Modules don't make instantiating templates any faster, it only makes parsing them faster.


They sure do, make the most common expansions avaiable as extern templates.


That has nothing to do with modules. You can extern a template going back to C++11.


Modules make it more convinient to do so, and allow for more build speedups.


Yes, with VC++ offering the best experience currently.


I am confused. Why was this downvoted? Is GCC and Clang doing better? (Or another C++ compiler?)


Because the answer is false, modules do not solve the problem of having common/shared template instantiations.

As for the comment about MSVC, it's mostly off topic, doesn't relate to the question that was asked (which had nothing to do with which compiler provides the best experience), and even if you accept that MSVC provides the best module experience, it's support for it is still very buggy and not suitable for anything other than experimenting and beta testing.


Rust also has (relatively) bad compile times due to generics. Not as bad as C++, but you can notice it's significantly worse than Go.


Rust does compile slower than Go, but it's not due to generics. It's mostly due to LLVM codegen and procedural macros. Go has the benefit of not relying on LLVM and not having procedural macros.


A lot of the IR comes from expanding huge stacks of generic functions. I don't think the issue can't be neatly partitioned up like that.


Absolutely true. I’d add that Rust is also doing a lot more expensive optimizations in release builds.


Until recently Go didn't even use registers for passing arguments, it just put everything on the stack like in a 1965 textbook (https://go.googlesource.com/proposal/+/refs/changes/78/24817...)


If you mean for function calls, I believe you're correct, not taking into account inline functions.

However, for code generation Go obviously uses registers since way before the 1.0 days.


You are right of course about LLVM being source of slowness.

In general discussion I see around is that all Rust's perf optimizations are to Rust's credit (nothing about LLVM optimization) while all woes around Rust's compile time lay at step of LLVM and Rust is not be blamed.


D, Ada, Eiffel, Delphi go as counter example, and C++ modules are already proving a much better experience in VC++.


Delphi didn't have generics. Not sure about more modern versions, but the power of the generics also impacts compile times. Java generics barely impact compile times at all but they're also nowhere as powerful as Rust and C++ ones.


You stop using Delphi long time ago, I would say.

To pick one of my examples, D templates and compile time metaprogramming are even more powerfull than Rust and C++ ones, yet it compiles just as fast.


Delphi added generics in 2009

So it had them roughly as long as Go existed


There is tinygo with llvm backend and the build times are abysmal.


That's not true, Rust doesn't have those headers and has the same compile time issues as C++.


Rust has macros and an expensive type system though, and compilation is getting faster.


This summary concisely captures the key thing about generics that has kept them out of Go for so long: they weren't created in a vacuum. Go's developers had the flaws in C++ and Java's approaches to draw on in their attempt to find a better approach. Many people calling for generics to have been implemented sooner were fine with those flaws in the other languages, but the Go team wanted to do better than that.


Generics exist in languages since 1976, more than enough examples than focusing on C++ and Java.


good point! What could the Go team glean from those languages to improve Go's generics implementation?


Lots of things, here is their acknowledgement that it was a mistake to ignore them.

> we were biased too much by experience with C++ without concepts and Java generics.

https://go.googlesource.com/proposal/+/master/design/go2draf...


If they wanted to avoid problems they would have implemented the feature right away, instead of making it a second thought for a supposedly modern language.


C# also made a similar tradeoff between purely generative generics of C++ and the type-erased generics of Java.


I only occasional do language design / compiler writing projects, or work with compiler internals.

Most people are really bad at estimating the implementation complexity and tradeoffs inherent in various language features. I'd say that C++ serves as a warning to others... think before you add features to your language, or you'll end up a total mess, like C++. Java and C# gave us some interesting and subtle lessons about what does and does not work about language features like generics. C++'s templates are just a total mess, all around. Java's generics are kind of a funny compile-time feature and have a ton of limitations. C# generics have some downright nasty interactions with other parts of the type system, like operator overloading.

IIRC the designers of C# have some regrets about how generics and other features were implemented. This is from an in-person talk, I don't have anything to cite.

You should take almost nobody at face value when they talk about language features like generics, because there is almost nobody with direct experience both designing and implementing those features. You should be pretty skeptical about what I'm saying too, IMO. But do believe that the design tradeoffs for generics are a complicated enough to justify the wait.

The Golang approach seems to strike a balance between extreme approaches. The C++ approach is "pay for what you use" which, in practice, is actually an extremist language design philosophy. In C++ / Rust, you are supposed to pay for templates only in code size and not in runtime. Everything is fully instantiated. The Haskell approach is "one abstraction fits all, everything is a pointer, use an implementation dictionary, monomorphization is an optimization". Again, something of an extremist approach (similar to Java's, but the comparison with Go / Haskell is better because Go / Haskell both use implementation dictionaries).

A middle approach is actually somewhat novel, believe it or not.


C++ still has the dubious honor of being the only language where I encountered code that I could not compile because my machine lacked enough memory.

I'm sure that's possible in other languages, but the program I was trying to compile wasn't that complicated... It had just been written by someone who believed every concrete class needed an abstract interface it was implementing.


I see you haven't worked with the joy of gradle & modern Java development. By the time you stack up gradle, javac itself, proguard or for Android R8 which are basically required to fix the terrible bytecode javac produces, some annotation processors because WHY NOT, custom build plugins, etc... it gets big


I highly doubt that javac produces terrible bytecode.


I highly doubt you've ever looked at it then. javac more or less intentionally doesn't do any optimizations, it leaves it all for the job of the JIT at runtime. It doesn't do either class-local optimizations nor whole-program / LTO-equivalent optimizations. Things like proguard offer both, and hence why they can so easily claim things like +20% performance. There's a lot of low-hanging fruit in javac's output, at the cost of needing a map file to reconstruct source mappings of course (although Java could of course integrate this into the class files if it wanted, it would just rather invest more or less exclusively in the JIT & GC).

Here for example: https://godbolt.org/z/4nzPY3h57 javac doesn't even bother to resolve that static field at compile time, which wouldn't even have observable side effects. Nope, just blindly emits a static initializer to invoke square(2) at runtime.

Or for a real fun one, enums: https://godbolt.org/z/xjbYvc8dx

Now some of that is necessary to handle all the stuff other classes could do with the enum, but some of it is just unnecessary. Like building the values array, which invokevirtuals all the ordinals of all the enum values. Ordinals the compiler already knew, since it's literally in the same .class file further up (and these are always in the same .class file, they are in lock-step, so there's again no observable difference if the values array was defined with constants instead of invoking ordinals on each field).

And then the trivial switch statement there isn't optimized at all, either, which again wouldn't be observable (compare against what eg. gcc would produce: https://godbolt.org/z/414rGjbE1 )

Further note that these examples are both static initializers, which means the JIT never "fixes" these (and thus instead perpetually contribute to slow startup). So if javac was going to bother with any attempt to optimize at all, you'd think it'd be here.


While those are low hanging fruit and I want to see more things like that done (I'd cry for more aot scalar replacement), I don't think those are examples of "bad bytecode".

Bad bytecode is the kludge of byte code you get when you try to write the same thing in kotlin: https://godbolt.org/z/9sGa6csa6


This would be the same thing in kotlin: https://godbolt.org/z/dq86ezsKd

And the bytecode is of comparable quality to javac's.


Computers seem to have caught up. The Java stack is the slowest one I regularly use (though it does support a certain amount of hot-recompile that other toolchains I use don't or can't), but it's never just crashed because it can't find room to store all those bytecode files.


I’ve compiled projects with javac, scalac, g++, gcc, and dmd. Javac is faster than all of them.


javac doesn't have to generate optimized machine code, which saves it a lot of time. But you pay the price incrementally in all those slow startups of java apps.


Only if stuck in Java 8, those of us in modern Java use JIT caches with PGO feedback.


You really need Scala to run out of memory though.


I've done that in lots of languages, but it usually means I was trying to crash the compiler :-)

I remember there was some simple way to crash a Haskell compiler with an out of memory error by defining a series of types, where each successive type required twice as much memory as the previous type to represent.


> I've done that in lots of languages, but it usually means I was trying to crash the compiler :-)

It's a real problem.

Anecdotally: I maintain a Debian package with a large-ish (but far from huge), template-heavy C++ codebase. I had to give up on compiling on 32 bit machines long ago, simply because not enough addressable memory was available to the compiler. I know other maintainers have to heavily tune the compiler to work around the problem, too.

I was once asked by the Ubuntu folks to disable parallel builds altogether, due to even their 64 bit builders running out of memory altogether.

And this package is just large-ish – it's nowhere near truly large (like a browser or an office suite).


I wasn't able to run the Clojure compiler in 2012 on a $150 dollar chromebook because it would OOM... but I blame myself for that one :P


>C# generics have some downright nasty interactions with other parts of the type system, like operator overloading.

Operators are static methods so why is that surprising or nasty?


It affects method overloading too, I should have said “method overloading”.

Just take a look at the rules for method overloading in C#, or take a look at one of the various C# compilers to see how methods are resolved.


Why are they an issue in C#, but not Java, which also has similar method overloading? Or are they related to default arguments?


You may be inferring something that I didn’t write. I didn’t once mention method overloading in Java.


Yes, I inferred because earlier you mentioned Java and C#, but only mentioned C# when it came to method overloading. Does Java have the same issue you were referring to when you mentioned C#?


I don't have enough Java experience to know the answer to that question.


> Hasn't this been a very long time coming?

That's a feature not a bug of golang. Major language changes like this by design are supposed to take a lot of time and thought to land.


[flagged]


Too late for what?


Programming languages are lacking because they are too stuck in the "implementation plane" while trying to deal with lots of "system design" problems. Generics, traits, interfaces, union types and others are fundamentally targeted at giving developers more expressive power to describe the systems we are designing. We know there are many parts we could swap around, using different implementations, connecting some pieces here and there... and the system should make sense and work. We can see that it must work! But these features are trying to resolve problems from a very closely-connected but still different domain, and that's why we see so much friction when trying to use them. We try to encode system-level patterns in the implementation, and there's gonna be friction. We can see that these features give us power, and that's why we like them, but we also see the problems they cause, and that's when we get cold feet and say "yeah... maybe it's not such a great idea".

I'm actually really happy to get generics in golang, and I'm happy with the team giving it as much thought as they need, but we are only gonna get so far within the current paradigm of trying to model the universe from a few text files. Generics are nice, but we shall do better in the future!


Right. Basically Go team is lacking big picture thinkers like this[1]

1. https://dilbert.com/strip/1994-12-17


Go generics were designed with plenty of cooperation from the PL research community. While some complexity to the design may be unavoidable, it's the farthest thing from just having a hacked-together feature with no "big picture" thinking underneath. Very similar to how generics were added to Java, in fact/


Golang is my favorite language, and I really like the approach that the team takes. A few days ago I shared here some interesting comments from Griesemer on Golang enums.

But sure, let's not give any ideas or question anything ever again, someone might get offended.


Honestly, the lack of enums annoys me far more than the generics ever do. Mainly because we have interfaces.


Some of the people behind go are the living example of how far you can get with a bunch of text files and a good model around them.

(The answer is very far)


We don't need Go for that, Oberon already made that point, we got a full graphical workstation OS out of it, in 1992.




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

Search: