back
640 comments

    > I can't answer a design question like whether to support 
    > generic methods, which is to say methods that are
    > parameterized separately from the receiver.
I work on the Dart language. Dart was initially designed with generic classes but not generic methods. Even at the time, some people on the team felt Dart should have had both.

We proceeded that way for several years. It was annoying, but tolerable because of Dart's optional type system -- you can sneak around the type checker really easily anyway, so in most cases you can just use "dynamic" instead of a generic method and get your code to run. Of course, it won't be type safe, but it will at least mostly do what you want.

When we later moved to a sound static type system, generic methods were a key part of that. Even though end users don't define their own generic methods very often, they use them all the time. Critical common core library methods like Iterable.map() are generic methods and need to be in order to be safely, precisely typed.

This is partially because functional-styled code is fairly idiomatic on Dart. You see lots of higher-order methods for things like manipulating sequences. Go has lambdas, but stylistically tends to be more imperative, so I'm not sure if they'll feel the same pressure.

I do think if you add generic types without generic methods, you will run into their lack. Methods are how you abstract over and reuse behavior. If you have generic methods without generic classes, you lose the ability to abstract over operations that happen to use generic classes.

A simple example is a constructor function. If you define a generic class that needs some kind of initialization (discouraged in Go, but it still happens), you really need that constructor to be generic too.

> You see lots of higher-order methods for things like manipulating sequences. Go has lambdas, but stylistically tends to be more imperative, so I'm not sure if they'll feel the same pressure.

`range` is generic, and by virtue of that is a builtin which only works with a subset of the also builtin magically generic types.

The reason why Go "does not feel the same pressure" has nothing to do with its imperative style[0] it is because they special-cased a few generic structures as builtin very early on, unlike, say, Java (which only had arrays as a typed datastucture).

[0] Java and C# are could hardly be more imperative, hell Java is only just adding anonymous functions

Ditto on generic methods both changing the game for Dart and being necessary. I spend most of my time in Dart, Swift and ObjC, and I've built serious applications in Erlang and Java. All of which I like for different reasons.

My opinion is that Dart's type system is the optimal type system. It allows for statically typed API surface, but at the same time, I can still add some dynamic voodoo under the surface and enable productive meta-programming.

I edited a book on Dart a few years ago. It's a shame the language hasn't gotten much traction outside of Google, as I enjoyed learning and using it.
I should send this to rsc, but it's fairly easy to find examples where the lack of generics caused an opportunity cost.

(1) I started porting our high-performance, concurrent cuckoo hashing code to Go about 4 years ago. I quit. You can probably guess why from the comments at the top of the file about boxing things with interface{}. It just got slow and gross, to the point where libcuckoo-go was slower and more bloated than the integrated map type, just because of all the boxing: https://github.com/efficient/go-cuckoo/blob/master/cuckoo.go

(my research group created libcuckoo.)

Go 1.9 offers a native concurrent map type, four years after we looked at getting libcuckoo on go -- because fundamental containers like this really benefit from being type-safe and fast.

(2) I chose to very tightly restrict the initial set of operations we initially accepted into the TensorFlow Go API because there was no non-gross way that I could see to manipulate Tensor types without adding the syntactic equivalent of the bigint library, where everything was Tensor.This(a, b), and Tensor.That(z, q). https://github.com/tensorflow/tensorflow/pull/1237 and https://github.com/tensorflow/tensorflow/pull/1771

I love go, but the lack of generics simply causes me to look elsewhere for certain large classes of development and research. We need them.

> fundamental containers like this really benefit from being type-safe

Note that, at least in its current form, the native concurrent map type uses interface{} for all keys and values, and therefore offers no type safety:

https://github.com/golang/go/blob/master/src/sync/map.go

See also: https://github.com/golang/go/issues/18177

All of Go's built-in pseudo-generic types (e.g. maps) require special support from the parser. I'm not sure if they plan on doing that for sync.Map as well, but this is clearly an area that could benefit from generics.

Thanks for this. Will followup on email.
Sounds like https://github.com/golang/go/issues/19335

I guess the performance issue has something to do with some missing optimizations. Hopefully it will get better.

Edit: Also https://github.com/golang/go/issues/19361

please do send that to rsc
For (2), are you looking for overloading arithmetic operators (+, -, etc.)? Do people normally consider that under the umbrella of "generics"?

For (1), for curiosity sake, I tried benchmarking the cuckoo.go file you posted. I'm curious if these numbers are in line with what you found. The first test I did was a Rand test, Putting 10,000 random string values under random string keys, then Getting the same 10,000 keys, then Getting 5000 more random keys. The first line below uses default code you posted, which uses

    type keytype string
    type valuetype string
The second line specializes the code to just using the string type directly for keys and values. The third line uses interface{} instead for values, which is the code style HN doesn't like.

    BenchmarkRandInsert-8                                100      13474408 ns/op
    BenchmarkStringStringRandInsert-8                    100      13585071 ns/op
    BenchmarkStringVoidStringRandInsert-8                100      14126666 ns/op
That third line shows the cost of casting to/from interface{}. The difference in the code was about as you might expect, with the "void" example needing a cast on each put and a cast on each get.

If I use uint32 for value instead, I get:

    BenchmarkStringIntRandInsert-8                	     200	   9431584 ns/op
    BenchmarkStringVoidIntRandInsert-8            	     100	  10485785 ns/op
The 9.4ms result is using a version of your code hand-specialized for string key and uint32 values. The 10.4ms result uses interface{} for values.

I ran a sequential-insert test as well, with keys and values generated sequentially instead of randomly. The results are much the same, though the cost of casting to/from interface{} seems to get mostly lost in the noise.

    BenchmarkSequentialInsert-8                          100      12185272 ns/op
    BenchmarkStringStringSequentialInsert-8              100      12233946 ns/op
    BenchmarkStringVoidStringSequentialInsert-8          100      12543980 ns/op
    BenchmarkStringIntSequentialInsert-8                 200       8823747 ns/op
    BenchmarkStringVoidIntSequentialInsert-8             200       8709242 ns/op
I also tried some tests using uint32 as the key type. This int key specialized version is 3x faster than any of the string key versions, but it's not really a fair comparison... a small part of the code is specific to string keys (getinthash), so it's not as obvious what the generic equivalent of that function would be. The keys need to be Hashable or some such, not just interface{}. I'm also allocating and formatting random strings in one case, vs just picking random numbers in the other case.

I didn't find the interface{} casting in any of the above code too horribly "gross", just a bit ugly, but that's entirely subjective. Yes, all of the cuckoo versions seemed about 30% slower than the corresponding builtin map type, but my benchmark numbers don't show if that is the price of boxing, of supporting concurrency, or maybe function call overhead, or something else. I'm guessing you did more detailed benchmarks that point to the source of the slowdown.

One last comment: keytype and valuetype really need to be exported, don't they? I wasn't able to use the library without changing them to exported symbols.

The paragraph I was looking for is this:

> For example, I've been examining generics recently, but I don't have in my mind a clear picture of the detailed, concrete problems that Go users need generics to solve. As a result, I can't answer a design question like whether to support generic methods, which is to say methods that are parameterized separately from the receiver. If we had a large set of real-world use cases, we could begin to answer a question like this by examining the significant ones.

This is a much more nuanced position than the Go team has expressed in the past, which amounted to "fuck generics," but it puts the onus on the community to come up with a set of scenarios where generics could solve significant issues. I wonder if Go's historical antipathy towards this feature has driven away most of the people who would want it, or if there is still enough latent desire for generics that serious Go users will be able to produce the necessary mountain of real-world use cases to get something going here.

Several members of the Go team have invested significant effort in studying generics and designing proposals, since before Go 1.0. For example, Ian Lance Taylor published several of his previous efforts, which had shortcomings he was dissatisfied with.

I believe your impression of the Go team's position has been corrupted (likely unintentionally) by intermediaries.

Java`s generics have had issues due to use site variance, plus the language isn't expressive enough, leading its users into a corner where they start wishing for reified generics (although arguably it's a case of missing the forest from the trees).

But even so, even with all the shortcomings, once Java 5 was released people migrated to usage of generics, even if generics in Java are totally optional by design.

My guess to why that happens is that the extra type safety and expressivity is definitely worth it in a language and without generics that type system ends up staying in your way. I personally can tolerate many things, but not a language without generics.

You might as well use a dynamic language. Not Python of course, but something like Erlang would definitely fit the bill for Google's notion of "systems programming".

The Go designers are right to not want to introduce generics though, because if you don't plan for generics from the get go, you inevitably end up with a broken implementation due to backwards compatibility concerns, just like Java before it.

But just like Java before it, Go will have half-assed generics. It's inevitable.

Personally I'm sad because Google had an opportunity to introduce a better language, given their marketing muscle. New mainstream languages are in fact a rare event. They had an opportunity here to really improve the status quo. And we got Go, yay!

I get that everyone would love to have a functional language that's eager by default with optional lazy constructs, great polymorphism, statically typed with inference, generics, great concurrency story, an efficient GC, that compiles quickly to self contained binaries with simple and effective tooling which takes only seconds to setup while giving you perfomance that equals java and can rival C, with a low memory footprint.

But, I don't know of one, and maybe that's because the Go team is right, some tradeoffs need to be made, and they did, and so Go is what it is. You can't add all the other great features you want and eat the Go cake too.

Disclaimer: I'm no language design expert. Just thinking this from the fact that I've yet to hear of such a language.

    > To minimize disruption, each change will require
    > careful thought, planning, and tooling, which in
    > turn limits the number of changes we can make.
    > Maybe we can do two or three, certainly not more than five.

    > ... I'm focusing today on possible major changes,
    > such as additional support for error handling, or
    > introducing immutable or read-only values, or adding
    > some form of generics, or other important topics
    > not yet suggested. We can do only a few of those
    > major changes. We will have to choose carefully.
This makes very little sense to me. If you _finally_ have the opportunity to break backwards-compatibility, just do it. Especially if, as he mentions earlier, they want to build tools to ease the transition from 1 to 2.

    > Once all the backwards-compatible work is done,
    > say in Go 1.20, then we can make the backwards-
    > incompatible changes in Go 2.0. If there turn out
    > to be no backwards-incompatible changes, maybe we
    > just declare that Go 1.20 is Go 2.0. Either way,
    > at that point we will transition from working on
    > the Go 1.X release sequence to working on the
    > Go 2.X sequence, perhaps with an extended support
    > window for the final Go 1.X release.
If there aren't any backwards-incompatible changes, why call it Go 2? Why confuse anyone?

---

Additionally, I'm of the opinion that more projects should adopt faster release cycles. The Linux kernel has a new release roughly every ~7-8 weeks. GitLab releases monthly. This allows a tight, quick iterate-and-feedback loop.

Set a timetable, and cut a release with whatever is ready at the time. If there are concerns of stability, you could do separate LTS releases. Two releases per year is far too short, I feel. Besides, isn't the whole idea of Go to go fast?

Here be Opinions:

I hate generics. also, I hate exceptions.

Too many people are wanting "magic" in their software. All some people want is to write the "Happy Path" through their code to get some Glory.

If it's your pet project to control your toilet with tweets then that's fine. But if it's for a program that will run 24/7 without human intervention then the code had better be plain, filled with the Unhappy Paths and boring.

Better one hour writing "if err" than two hours looking at logs at ohshit.30am.

> For example, I've been examining generics recently, but I don't have in my mind a clear picture of the detailed, concrete problems that Go users need generics to solve.

This is sampling bias at work. The people who need generics have long since given up on Go and no longer even bother participating in Go-related discussions, because they've believe it will never happen. Meanwhile, if you're still using Go, you must have use cases where the lack of generics is not a problem and the existing language features are good enough. Sampling Go users to try and find compelling use cases for adding generics is not going to yield any useful data almost by definition.

> For example, I've been examining generics recently, but I don't have in my mind a clear picture of the detailed, concrete problems that Go users need generics to solve. […] If we had a large set of real-world use cases, we could begin to answer a question like this by examining the significant ones.

Not implementing generics, then suggesting that it would be nice to have examples of generics being used in the wild… You had it coming, obviously.

Now what's the next step, refusing to implement generics because nobody uses it?

> Every major potential change to Go should be motivated by one or more experience reports documenting how people use Go today and why that's not working well enough.

My goodness, it looks like that is the next step. Go users have put up with the absence of generics, so they're not likely to complain too loudly at this point (besides, I hear the empty interface escape hatch, while not very safe, does work). More exacting developers have probably dismissed Go from the outset, so the won't be able to provide those experience reports.

Go doesn't have const structs, maps or other objects:

https://stackoverflow.com/questions/43368604/constant-struct...

https://stackoverflow.com/questions/18342195/how-to-declare-...

This is a remarkable oversight which makes it impossible to write purely-functional code with Go. We also see this same problem in most other imperative languages, with organizations going to great lengths to emulate const data:

https://facebook.github.io/immutable-js/

Const-ness in the spirit of languages like Clojure would seem to be a relatively straightforward feature to add, so I don't really understand the philosophy of leaving it out. Hopefully someone here knows and can enlighten us!

There are many comments griping about generics. There are many comments griping about the Go team daring to even ask what problems the lack of generics cause.

But take a look at this article about the design goals of Go: https://talks.golang.org/2012/splash.article Look especially at section 4, "Pain Points". That is what Go is trying to solve. So what the Go team is asking for, I suspect, is concrete ways that the lack of generics hinders Go from solving those problems.

You say those aren't your problems? That's fine. You're free to use Go for your problems, but you aren't their target audience. Feel free to use another language that is more to your liking.

Note well: I'm not on the Go team, and I don't speak for them. This is my impression of what's going on - that there's a disconnect in what they're asking for and what the comments here are supplying.

(And by the way, for those here who say - or imply - that the Go team is ignorant of other languages and techniques, note in section 7 the casual way they say "oh, yeah, this technique has been used since the 1970s, Modula 2 and Ada used it, so don't think we're so brilliant to have come up with this one". These people know their stuff, they know their history, they know more languages than you think they do. They probably know more languages than you do - even pjmlp. Stop assuming they're ignorant of how generics are done in other languages. Seriously. Just stop it.)

As much as I want them to fix the big things like lack of generics, I hope they fix some of the little things that the compiler doesn't catch but could/should. One that comes to mind is how easy it is to accidentally write:

  for foo := range(bar)
Instead of:

  for _, foo := range(bar)
When you just want to iterate over the contents of a slice and don't care about the indices. Failing to unpack both the index and the value should be a compile error.
The beauty of Go is that you get developer productivity pretty close to Ruby/Python levels with performance that is similar to Java/C++

Improvements to package management is probably the highest item on my wishlist for Go 2.

Generics have never stopped me from building in Go... But without them I often do my prototyping in python, javascript, or php.

Working with batch processing I'm often changing my maps to lists or hashes multiple times during discovery. Go makes me rewrite all my code each time I change the variable type.

How about fixing all the GOPATH crap?
As a ruby and go dev, I'm a bit sad to see backward-compatibility going. Thinking I could write code with minimum dependencies and that would just work as is years later was really refreshing compared to the high level of maintenance needed in a ruby app.

But well, I trust the core team to make the best choices.

I like Go concept: very simple and minimalistic language, yet usable enough for many projects, even at cost of some repetition. Generics are not a concern for me. But error handling is the thing I don't like at all. I think that exceptions are best construct for error handling: they are not invasive and if you didn't handle error, it won't die silently, you have to be explicit about that. In my programs there's very little error handling, usually some generic handling at layer boundaries (unhandled exception leads to transaction rollback; unhandled exception returns as HTTP 500, etc) and very few cases when I want to handle it differently. And this produces correct and reliable program with very little effort. Now with Go I must handle every error. If I'm lazy, I'm handling it with `if err != nil { return err; }`, but this style doesn't preserve stack trace and it might be hard to understand what's going on. If I want to wrap original error, standard library don't even have this pattern, I have to roll my own wrapper or use 3-rd library for such a core concept.

What I'd like is some kind of automatic error propagation, so any unhandled error will return from function wrapped with some special class with enough information to find out what happened.

> For example, I've been examining generics recently, but I don't have in my mind a clear picture of the detailed, concrete problems that Go users need generics to solve.

Collections?

But I always heard never to use Go 2! :P
"We estimate that there are at least half a million Go developers worldwide, which means there are millions of Go source files and at least a billion of lines of Go code"
I must say that whenever there is a discussion about the merits of the Go programming language, it really feels hostile in the discussion thread. It seems that people are seriously angry that others even consider using the language. It is sort of painful reading through the responses which implicitly declare that anybody who enjoys programming with Go is clueless.

It also really makes me wonder if I am living in some sort of alternate reality. I am a professional programmer working at a large company and I am pretty sure that 95% of my colleagues (myself included, as difficult as it is for me to admit) have no idea what a reified generic is. I have run into some problems where being able to define custom generic containers would be nice, but I don't feel like that has seriously hindered my ability to deliver safe, functional, and maintainable software.

What I appreciate most about Go is that I am sure that I can look at 99% of the Go code written in the world and I can understand it immediately. When maintaining large code bases with many developers of differing skill levels, this advantage can't be understated. That is the reason there are so many successful new programs popping up in Go with large open-source communities. It is because Go is accessible and friendly to people of varying skill levels, unlike most of the opinions expressed in this thread.

Sorry, but if "a major cloud platform suffers a production outage at midnight" is the bar for effecting change in Go, then I want no part of it.
"Go 2 considered harmful" - Edsger Dijkstra, 1968
This is a frustratingly common way to design mainstream PLs: "show me the use case". I've seen it firsthand for a number of big PL projects. People are trapped in their little bubble. My approach is to be wildly polyglot, actively searching for good ideas in other languages. Also, I try to write complex code outside the intended domain space and understand why it's harder than it should be.

For example, in Go it's difficult to implement state machines in a clean way. Another is handling events and timeouts is easier with Reactive programming. Distributed programming is easier with Erlang or Akka. Don't wait for problem reports in the Go community. Look at the problems in other PLs and proactively improve Go.

I would love to see uniform-function-call-syntax.

Turning: func (f Foo) name() string

Into: func name(f Foo) string

Callable like this: f.name() or name(f)

Extending foreign structs from another package should be possible too, just without access to private fields.

Other than that, if-as-expression would be nice to have, too.

The leap second problem reminds me of this post[0].

[0] https://news.ycombinator.com/item?id=14121780

Disclaimer: I mean this with love

This post really frustrates me, because the lengthy discussion about identifying problems and implementing solutions is pure BS. Go read the years worth of tickets asking for monotonic time, and see how notable names in the core team responded. Pick any particular issue people commonly have with golang, and you'll likely find a ticket with the same pattern: overt dismissal, with a heavy moralizing tone that you should feel bad for even asking about the issue. It's infuriating that the same people making those comments are now taking credit for the solution, when they had to be dragged into even admitting the issue was legitimate.

Regarding the lacking of generics problem, is there a way to get around it, there are always plenty of tools doing that, if the IDE can patch the syntax and support certain kind of prigma to generate the template code, then the problem is almost solved, not sure if it'll cover all cases like Java does though.
This was announced at GopherCon today. FYI, if folks are interested in following along other conference proceedings, there is no livestream, but there is an official liveblog: https://sourcegraph.com/gophercon
"Play it cool" https://youtu.be/BHIo6qwJarI Go! by Public Service Broadcasting.

(Sorry just discovered this song a few days ago)

about generics : i've never had a deep look at it but i've always wondered if most of the problem couldn't be solved by having base types ( int, string, date, float, ..) implements fundamental interfaces (sortable, hashable, etc). i suppose that if the solution were that simple people would've already thought about it.

in particular, i think it could help with the method dispatch, but probably not with the memory allocation ( although go already uses interfaces pretty extensively).

Most of the discussion here seems to be around generics, and it sounds like they still don't see the benefit of generics.

I like Go, but the maintainers have a maddeningly stubborn attitude towards generics and package managers and won't ease up even with many voices asking for these features.

> We did what we always do when there's a problem without a clear solution: we waited. Waiting gives us more time to add experience and understanding of the problem and also more time to find a good solution. In this case, waiting added to our understanding of the significance of the problem, in the form of a thankfully minor outage at Cloudflare. Their Go code timed DNS requests during the end-of-2016 leap second as taking around negative 990 milliseconds, which caused simultaneous panics across their servers, breaking 0.2% of DNS queries at peak.

This is absurd. Waiting to fix a known language design issue until a production outage of a major customer is a failure of process, not an achievement. The fact that the post presents this as a positive aspect of Go's development process is beyond comprehension to me.

I didn't expect to get namechecked in that.

Shows the value of constantly being transparent and supporting open source projects.

Forget generics. What I missed most are algebraic data structures.
I was newly married and my husband and I intended on getting a house but our credit scores were low and I also had a few bank debts. I spoke to a friend about it who happens to be a tech guy and he introduced me to a hacker (blackbutcher). I contacted blackbutcher and he helped my husband and I boost our credit scores and he also helped me clear my bank debts. This hacker is a genius and comes highly recommended by a lot of people. He's affordable and genuine unlike a lot of fakes I saw on the internet. Contact him on his mail blackbutcher.hacker@outlook.com