Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Honest question:

Do you think Generics will be an overall win for the Go language, or will they be overused / end up making code harder to read / harder reason about?

I kind of hate looking at Go code containing generics. What previously was elegant and easy on the eyes is now annoying and muddled by the additional layer of abstraction. I'm saying this with sadness, as someone who fell in love with Go back in 2012 and still writes it at least weekly.

Is it a better bet to move on and go full Rust, rather than bother with wherever the goggle golang train is headed?

p.s. Even though code generation is [also] annoying and perhaps not ideal, I've rarely needed to use it and kind of liked that it was inconvenient - forcing me to think about problems differently. Certainly some problems will benefit from the addition of generics, but is it really enough to justify the added complexity? I wonder if this is a case of tragedy due to vocal minority.

p.p.s. Generics in other languages like Java or Scala seem fine, as they are "kitchen sink"-style "all things to all people" languages. Such behemoths are nearly always clunkier and less easy to read than pre-generics Golang.



> Is it a better bet to move on and go full Rust, rather than bother with wherever the goggle golang train is headed?

Ecosystem aside, with Rust you'll get far better language design (generics fully integrated with everything (stdlib, consts, all libraries), sane error handling, sane dependency management, no "any" type, editions, and more. More complexity too though.

> Certainly some problems will benefit from the addition of generics, but is it really enough to justify the added complexity?

Coming from languages that have them, it's just hard to take Golang seriously, where every library either ditches type safety (more runtime errors I wouldn't have with other language), or forces you to copy-paste code just because you need support for some new type (more boilerplate to maintain == more errors). Or reinvents generics with code generation and aboriginal characters.

Once you start using generics, they really aren't complex.


> Coming from languages that have them, it's just hard to take Golang seriously

Some people judge the language on their ability to get work done with it.

I get generics, but they really don't come up in daily use with the tasks where I use Go. Yes, it would be nice for writing some libraries but you're going through a laundry list of Rust features which don't hamper my ability to get work done at all.

I even like Rust, but if I'm going to write a worker that will read from a queue, do a transformation and write to a few more queues/services upon completion, Go just works and the turn around time is far better than Rust. It's like was Perl was for Unix, but for the cloud instead.


Agreed.

I wrote a bunch of Rust, Scala, Haskell, but I still greatly prefer Go, even without generics.

I am very happy with the generic container libraries I'll get with generics, but I hope people won't try to be too clever (as they usually do). So far, Go is a language that just rules out a lot of bikeshedding, which I very much appreciate. We'll see how that evolves.

I do think premature or wrong abstractions are a much bigger and widespread problem than a lack of abstraction.

Also, I really like Go's error handling, it results in error messages in Go projects usually being top-notch (because of explicit handling and wrapping which includes relevant context and human-readable messages).


I'm with you. Go has its problems, and I am aware of them, but it is just so compatible with the way my brain works, it is amazing.

I remember listening to a podcast about C++, and the guest explained how after working with C++ for about five years, they still encountered aspects of the language that surprised them on a regular basis (to be fair, though, that was before C++11). To me, Go just clicks in way few languages did.


I think language preference is very much about how people’s brains work (and they all work differently!). My brain struggles with Go, but clicks with Rust.


This must be it and ditto for me on Rust. It feels like they made the language just for me.


Interesting - I've also written a ton of code in those languages (not as much in Haskell), and Go would be my last choice by a large margin (and Rust my favorite by a wide margin). I went through a love/hate relationship for a while (it lets me create static executable, compiles pretty fast, it is easy to remember, etc. but if I have to type "if err == nil" one more time!!!).

While I applaud the focus on simplicity, I found it simply transfers that burden to the programmer (I have to loop over a map to clear it...really?). Every single "lack of" feature in Go (has nil, no sum type errors, no pattern matching, etc.) is in Rust which gives me endless freedom to express safe, correct programs. I suppose language choice is highly individual, but it still perplexes me as to what people see in Go over Rust.


I'm sure everyone's experiences differ, but in my travels Go has shown to carry the lowest rate of "who wrote this shit?" when you come back to a project five years later. I agree that it puts the burden onto the programmer upfront, but reduces it down the road. Tradeoffs...

I have a lot more fun writing code in other languages. I enjoy not having that burden on me while writing code. In an earlier life that would have been important to me. Now that I'm old and curmudgeony I've started to value other things.


I work in a team, where not all are unicorn Rust specialists. The work we do doesn't require millisecond-level response times, nor is it something that needs to be ultra-safe in relation to memory safety.

I can teach any mook with basic Java/C# programming knowledge how to be productive in Go in less than a week. At this point they can read pretty much any Go code pretty fluently and can be trusted not to commit anything stupid.

Can you say the same about Rust?


> I work in a team, where not all are unicorn Rust specialists

> I can teach any mook with basic Java/C# programming knowledge how to be productive in Go in less than a week.

This is fair, and probably the reason why Go continues to be popular I guess

> The work we do doesn't require millisecond-level response times

Rust is a high level language, and it is a bit of a misnomer that it is only good for low level things. Most of my stuff doesn't require this level of speed either (the previous major version of my project was written in _Python_). I use Rust for the safety and data structure benefits, not speed.

> can be trusted not to commit anything stupid

As a Rust coder, and a fan of functional programming as well, I personally find any "null pointer" error quite "stupid" and unnecessary as is the occasional "err == nil" instead of "err != nil", or forgetting to check it at all. We will probably have to disagree on what constitutes stupidity, and that is fine.


Absolutely. You can write some Rust by following some hello world tutorial and be productive in 15 minutes. That doesn't mean you'd be proficient in writing procedural macros or understanding all details of memory management, but I've seen Linux magazines presenting Rust omitting all those "advanced" features and surprising amount of Rust users is new to programming.

The learning curve is usually different because you will need to understand more before program will compile and because most materials aims to cover 100% of the language from day one, but if you want to approach it differently, productivity wouldn't be a problem.


Just so we're clear, you're saying that Rust only takes 15 minutes to learn and be productive in, for the average person?


Basic Rust, sure. For some definition of "basic", and average person that knows some other programming language. What I mean is something along this line:

https://www.linuxjournal.com/content/getting-started-rust-wo...

It leaves many concepts without full explanation, but that's not necessary to do something useful.


Premature or wrong anything is clearly bad. But is parameterisation a premature or wrong abstraction? I'd say that parameterisation - even in the type language - is the original proven abstraction.


I think this is why I always have trouble understanding arguments against generics (in the general case at least). Parameters are added to functions/methods in order to make them more generic/flexible, as a rule. To move something that may have been hardcoded into something that can now be configured:

  get_data() { filename=default ... }
  =becomes=>
  get_data(filename=default) { ... }
When the type either does not matter, but the concrete instance records it, or the type makes sense to be configurable, you want generics. As a silly example (but short enough to fit into a comment block):

  fn nth(seq: [int], n: int) -> int
Now you have to make a new nth for every single sequence type, even though it has no bearings on the actual operation. Or you make it generic:

  fn nth<T>(seq: [T], n: int) -> T
That's a trivia case, yes. But if anyone has ever worked on a complex code base there are plenty of situations like this that turn up, at least in my experience. Sure, I almost always start off with concrete instances with a fixed type, but as soon as it becomes apparent that the type itself is irrelevant and I have a couple use-cases with different types, why not make it generic and be done with it? Like, would you really have more than one version of that get_data function running around, one for every conceivable filename? That would be obscene. Why would you do the same with types?


Yes, in my opinion oftentimes it is.

There is a reason why even in mathematics people like to operate on concrete examples to get an intuition. For many, concrete is much easier to understand than abstract.

That's the less important point. The more important point is that making your code generic often involves more trickery which makes the code more complex, even if you only use the code once or twice - so that's just effort wasted.

The fact that parameterization is a proven abstraction doesn't mean it's good everywhere. Same as I don't agree with the "Clean Code" way of creating a myriad of 4 line functions.

Yes, good programmers won't make these mistakes, you can totally handle them. But when arriving at legacy code or open-source projects I greatly prefer to find under-abstraction rather than over-abstraction.

To be clear, I'm not against generics, I just agree with the parent of my original message. I'm worried people will overuse them and I don't want a whole laundry list of Rust features in Go. I'm very happy about libraries with type-safe generic B-Trees.


I get what you mean, and I personally rarely write generics, but when I need them they're great. I think that a good rule of thumb that would be easy to implement and review would be "no generics outside of libraries". This way, you get your type-safe containers, your application code doesn't get much more complex than before, and if you want to introduce generics, you have to really think about it.


> I get generics, but they really don't come up in daily use with the tasks where I use Go.

I sort of understand this argument, but I can't really imagine defining queue as something else then <T> wait_for_item() -> T. I've been writing Python for to long to know that wait_for_item() -> any will backfire in production eventually and I don't want that. At some scale (both in code size and amount and scope of dependencies) those problems just become too serious and too common to not have language that deals with that. And Go is way too popular for people to limit its use only for the cases where it currently works.


Define queue using generics, sure. It will be huge for people waiting packages.

A specific queue is typically well defined and has a struct in/out.

There are times when I've wanted arbitrarily nested JSON that doesn't map into structs very well, but it is uncommon enough.


I guess a better response to this is that I don't use generics a lot when writing Go, but I probably use a lot of packages where they would have been incredibly useful.


> if I'm going to write a worker that will read from a queue, do a transformation and write to a few more queues/services upon completion, Go just works and the turn around time is far better than Rust.

Since the inception of async/await in Rust, it is incredibly quick to whip something like that up. The slowest part might even be the time it takes to compile. Maybe that's what you were referring to?


The way Go does it is easier to reason with, channels and goroutines are really easy to explain to anyone.


That does sound a lot easier than Rust's channels and tasks...


> Some people judge the language on their ability to get work done with it.

Really? Safety and correctness aren't relevant for you? Then why even bother with go instead of Python or a Lisp?

For me it's crucial that a programming language contains tools that allow me to definitely rule out as many errors as possible. A powerful (and sound) typesystem does just that.


How are you finding Idris? or are you more in the Agda camp?

If you want to definitely rule out as many errors as possible, dependently typed languages are the state of the art, allowing you to write a sort function that will fail to compile if it returns a list that isn't sorted (eg,https://dafoster.net/articles/2015/02/27/proof-terms-in-idri..., or https://www.twanvl.nl/blog/agda/sorting).

After all, if you can't even prove basic properties about your code from your language, like array accesses being within bounds, are you really using all possible tools to rule out errors at your disposal?


To be fair, I never had a chance to use Idris nor Agda. I don't know how capable I would be to encode the proofs in libraries nor client code. If it's usable, I am all for it.

Otoh, I do know that languages like OCaml, Haskell, or Rust take the burden of trivial errors from my shoulders for neglible cost.


Yeah, that was an insincere gotcha question, and it's a shame that it could potentially undermine other readers' potential value of a higher degree of confidence versus a hypothetically perfect confidence.


Does Rust also have a feature that instills a burning desire to proselytise?


I like Rust mostly for its user-friendly tools. I dislike the compilation model. The type system I already liked even before Rust existed. So, not every String opinion on PL is down to Rust.


No, I just find discussions like these absolutely hilarious. A Go developer with ten years of experience convinces himself that Go is the gospel and generics are a useless toy and distraction from "real work™", then tries generics and they move from a feature you don't really need to a feature you couldn't possibly live without.

Same with every other thing that goes into this language. A thing that's been available elsewhere for literally 3-4 decades.

We have some examples in this very thread.


Your description doesn’t match what I see in this thread at all.

Odd that you’re so upset by other people’s choices.


Yes, all Rust devs get infected with the PROSELTIZE virus. Other languages are desperately trying to develop vaccines.


> > Some people judge the language on their ability to get work done with it.

> Really? Safety and correctness aren't relevant for you? Then why even bother with go instead of Python or a Lisp?

A charitable reading of the GP would be that "safety and correctness" would be included in "getting work done", in amounts that are appropriate to the work in question. Your interpretation is... less than charitable.


Exactly. Go is safe enough. Correctness for most programs is more about making sure it solves the problem correctly, not merely that it guarantees no errors if it runs. That feels like a modern version of "It compiles, I'm done."


Maybe they don't want a target directory using 3gb from a clean build.


It's already self-evident that Go manages that just fine. That's just weird dogma.


> It's like [what] Perl was for Unix, but for the cloud instead.

Perl and Go both have the kind of long-term language stability that I value above all.

But Go offers excellent concurrency, networking baked-in, and now even fuzzing.

https://go.dev/blog/fuzz-beta


I use Go every day and yeah you can your work done with it but not quickly due to typing all the boilerplate.


> Some people judge the language on their ability to get work done with it.

I value maintenability (type safety helps prevent unintended consequences) and readability (Haskell and Clojure are out).


I haven't even written that much go, but even with the little I have written, I have felt hindered in my "ability to get work done" by the lack of generics.


That comparison is a disowner for Perl, given its language capabilities.


It's a disowner for Go given that Perl's philosophy is "more is more" and Go's philosophy is the Wirthian "less is more".


There is a market for a language like Rust but with garbage collection and reflection. There is OCaml, but it's not for the modern developer. Go with generics is the closest thing to that which is getting some use.


This more|less also describes Nim. The recent automatic memory management ARC/ORC alternative is not really even what most people think of as "garbage collection". Its macros give you full AST accept & re-emit powers and it's had generics since before Go existed. I realize it probably does not score highly on "gets used", but it deserves more attention.


I think so too but I think the languages that fit the bill are Kotlin and Swift. Both modern syntax, great generics w/GC and ARC respectively.

Go w/generics falls very short IMO, expressibility, type safety and poor null handling all rule it out as a reasonable stand-in for Rust.


> There is a market for a language like Rust but with garbage collection and reflection.

That's why languages like JS/TS, Haskell, Elixir, OCaml (which is way more modern than Go), ... exist and are used.


That language is Swift. It's got a lot of similarities with rust, just with everything being ref counted.

If only its ecosystem was more platform agnostic.


Swift is pretty much that language. It doesn't have the imitable crate ecosystem or tooling, though.


Why not Ocaml?


Ocaml's ecosystem is a total disaster. Do you use the lousy included standard library? Do you use Jane Street Base? Jane Street Core? Async? Lwt? Batteries? Containers? Iter? Esy? Opam? Ocamlbuild? Dune? Have you seen Dune's documentation? And Facebook fractured the ecosystem even more with Reason, obnoxiously.


Slight nit: Rust does have an `Any` type[0] (and has since at least 1.0). Unlike Go's `interface{}`/`Any` type though, it's actually type-safe; the only way to get a value from it is to try to downcast into a concrete type, which returns an Option and will always be `None` if it doesn't match the type.

[0]: https://doc.rust-lang.org/std/any/trait.Any.html


That's generally how interface{} works in Go too. In local idioms, but the same effect. Only valid operations are permitted.

If you continue to disagree with me, please be specific about what operation Go permits on interface{} values that you consider type-unsafe.


The trailing suffix cast (not sure the actual name, but `x.(string)` and the like) are not type safe. Yes, it's not required to be able to try to deal with an empty interface type, but it's there, and it will always compile fine and allow code afterwards to freely assume the cast succeeded without any errors.

I imagine the rebuttal to this is that you could always just manually `unwrap` the `Option` that is returned by the downcast methods in Rust, but I pretty strongly feel that adding explicit syntax for this type of unsafe cast normalizes it to an extent that having an `unwrap` method on a generic Option type doesn't remotely approach. It would be quite a stretch to argue that having the `unwrap` method on Option is explicitly a endorsement on unwrapping on the downcast methods given that Option is used for far more than just that (and especially given the huge amount of stigma that using `unwrap` gets in the community, which is mostly fair but sometimes goes a little overboard). On the other hand, having specific syntax that is used for unsafe casts and nothing else is a pretty explicit argument that it should be done sometimes, or else it wouldn't be in the language at all. Go could pretty easily have gone the route they did with map lookups and had the unsafe casts return two values, the latter of which is a boolean indicating success or failure (and in the cast of `false` being returned, the former value would just be the zero value for the output type), and the fact that this wasn't done means that ergonomics was prioritized over safety.


"Yes, it's not required to be able to try to deal with an empty interface type, but it's there, and it will always compile fine and allow code afterwards to freely assume the cast succeeded without any errors."

That is incorrect. It issues a runtime panic, which is the same as the syntax for Rust that will do the same thing. Or you can use the "x, isX = y.(SomeType)" syntax and it will tell you whether it matches or not.

It's the exact same functionality just spelled differently, but there is no scenario where you have an int but you call it a string and the code simply proceeds along and does whatever.

"unsafe casts return two values"

It does do that! It's done it since the beginning. It's not a cast, though. It's a "type assertion". It can't convert. Go only has casting for safe conversions... well, things most programmers consider safe. I don't consider int -> byte "safe" but I am in the minority on that.

You need to stop talking about Go. You don't know it. There's nothing wrong with not knowing it, but you shouldn't combine that with trying to explain it to people. It isn't as crazy as you think. It is definitely type-safe. The "type safe" that it is is less rich and complex than Rust or Haskell, but it is type safe within its type system, subject to the usual "unsafe" caveat. If it weren't, it would never had needed generics... it would be a dynamic language and they build generics in so deep they aren't even "generics", they're just how the language works at all. The whole reason Go needs generics is precisely that it isn't type-unsafe.


> That is incorrect. It issues a runtime panic, which is the same as the syntax for Rust that will do the same thing.

As I said before, having explicit syntax for it is a very different thing than having a method for it on a generic type that isn't specific to it

> It does do that! It's done it since the beginning

That's good! Still isn't required though, and having a safe way to do something doesn't mean that the unsafe way doesn't exist

> It's not a cast, though. It's a "type assertion". It can't convert.

Okay? It still lets you get errors due to the type system not preventing them

> It is definitely type-safe. The "type safe" that it is is less rich and complex than Rust or Haskell, but it is type safe within its type system

I agree that type safety is a spectrum, and very few languiages are fully type safe. I probably should have been more clear that I wasn't saying that Go wasn't 100% unsafe, but I thought it would be obvious I wasn't saying that. Clearly that's not the case.

> If it weren't, it would never had needed generics... it would be a dynamic language and they build generics in so deep they aren't even "generics", they're just how the language works at all. The whole reason Go needs generics is precisely that it isn't type-unsafe.

I'm not sure what this means; pretty much every statically typed language gets benefits from generics, and they're clearly not all equally type safe, so I don't know what this is supposed to convince me of.

> You need to stop talking about Go. You don't know it.

I don't know everything about Go, that's true. Go doesn't have a special definition of type safety though, and recognizing places where it isn't type safe doesn't require complete knowledge of the entire language.


They are called type assertions, I believe they are type safe, albeit at runtime. It will panic if the assertion is incorrect.


Is Rust really a suitable replacement for Go? I mean I know they are both system languages but I feel they have different use cases completely.


I'm a Rust "fanboy" (if one wants to say so), and I still think that the use cases for Rust are just a minority in the landscape of modern languages (that is, where there is a choice).

Rust has a mind-boggling overhead, for many reasons (and I'm not talking about the borrow checker, which I think one gets used to after some time), even if the language itself is consistent and ergonomic (within the intended constraints).

To me, they have very different use cases - one will definitely know when Rust is required or it's an appropriate pick. For the rest, Go is fine. I think that those who put them on the same basket, haven't really used one of them.

Regarding systems programming, my opinion is that they require the lack of a runtime (not just because of the performance, but also, for the framework(s) written with low-level primitives in mind), but this is arguable (in particular, there's no clearcut definition of what systems programming is).


Also a Rust "fanboy" and I would disagree. I'm amazed how productive I am in Rust (about 6 months in). I've written tons of Go as well, and I write Rust faster (What I was expecting was to write better code, but have it take longer, but turned out not to be true). I also thought the language would be too "low level" for the type code I write (I wrote in Go and Python mostly before, though I've written in many languages in the past), but I found it scales very well up and down to low and high level challenges (Serde _rocks_). At this point, I use Rust for everything except a couple hundred line script (Python for that).

I'm not saying everyone would fit in that camp and it is a harder language to get started in for sure, but I think the borrow checker scares away too many people. It is a learning curve, but when it clicks, you will realize that every language has ownership and borrowing... you just didn't realize it because the GC allowed you to be sloppy about it. Once you do, it makes you a better programmer (just like coding in Haskell does).


> I've written tons of Go as well, and I write Rust faster

The overhead in Rust, when compared to comparable languages, is very concrete. On top of my head:

- using hash maps is more convoluted (in some cases, arrays also need some boilerplate as well)

- bidirectional/circual references need to be handled (and are also ugly); algorithms programming in Rust is typically more complex because of this

- lifetimes (which also spread virally when introduced)

- explicit allocation types

- (smart) pointer types in general

- interior mutability; which may also required additional design (including: will the mutexes destroy performance?)

Some of them intersect with each other and pile up (allocation types; pointer types; interior mutability).

There is certainly overhead in Golang (I think it's not very ergonomic for a functional style, for example), but it's nothing comparable.

Overhead takes time; unless one has a time machine, it makes a programming language concretely "slower".

The above is just the concrete overhead. The abstract overhead (=things to keep in mind) is another story (e.g. proliferation of types because of the ownership, traits...). I understand, say, that path types are a necessary evil, but they're surely ugly to handle.

> you just didn't realize it because the GC allowed you to be sloppy about it

It's not sloppy where it's not needed. A significant part of the Rust overhead is due to the rigorous philosophy of the language, which enforces constraints also when they're not required. This is absolutely fine, but it's not realistic to think that it has no cost.


I thought this as well before I had used the language for major projects, but in practice, I found it not the case (at least for me). Your list of concerns I do not find to be anything I think about day to day as I'm coding. I just write code normally for the most part. Yes, you do have to think about your data structures and how you will use them, but in other languages I found myself redesigning these later because I had come up with the wrong paradigm - in Rust I find myself getting these correct the first time, so perhaps the restrictions I find helpful here (I suspect the earlier poster commenting how certain languages fit your thinking patterns may be on to something).

My Rust code typically 'just works' the first time or close to it (something I haven't experienced since writing Haskell and Ocaml), but in other languages I would not experience this, and I'd spend more time debugging. I have gotten stuck a few times in Rust as part of my learning journey, but overall, I'm still proceeding at least as fast if not faster than I wrote in Go and other languages.

Rust is definitely not perfect, and I see some of the warts, but I don't want to write a major project in anything else at this point.


Ironically I've been using a lot of hashmaps in some code recently...and the rust implementation is pretty damn ergonomic.

Certainly better than C++ or C# IMHO, and even my Python colleagues were amazed how easy it was to work with.


I have only touched Rust & Go but I would only consider Rust where I would previously have used C or C++. The stuff that needs to be fast, low memory, and what not. I see Go as more of a Java. Good for backend systems. Also CLIs.


> they are both system languages

Due to ability to work without stdlib, Rust is system language in a sense Go never will be, to the point Go authors withdrawn this definition.

> Is Rust really a suitable replacement for Go?

It depends on your requirements for the ecosystem. If you need compatibility with Go or Go libraries, than it's not a replacement.

Libraries and support aside, any program written in Go can be written in Rust (and going back to nostdlib, many programs written in Rust cannot be written in Go). If you have required libraries, I'd say it can be written comparably quickly and easy. For example you can have web service returning hello world in 10 lines of code or so in either.

They are popular in different circles, but that is mostly not related to technical abilities. For 99% of of applications, you could pick either one.


Rust is overused where Haskell or OCaml is more appropriate, simply because it people prefer the general quality of it over more mainstream languages even at the cost of putting up with memory management minutiae. Simply put, one rarely every need to use Rust in a better world.

This is a good direction for Go, which will either lead to Go having a better ecosystem, or remove ideological barriers from people keeping on using Go.


> Rust is overused where Haskell or OCaml is more appropriate

Except then I'd have to learn Haskell or OCaml :) As a curly-bracket language programmer, Rust was much easier for me to get into and feels more like home.


I found this closed-mindedness hard to understand -- I don't spend very much conscious thought on the syntax when programming at all -- but for people like you facebook made Reason ML https://reasonml.github.io/

Someone should port OCaml to the Go runtime with a good high-level FFI. It could really give the community a boost.


The actual need for generics is, I think, overstated. If you are writing a very abstract library, maybe, but most problems that I have encountered don't even begin to approach needing them.


> Or reinvents generics with code generation and aboriginal characters.

Tell me you’ve never used Go without telling me you’ve never used Go.


I believe OP is referring to this https://github.com/vasilevp/aboriginal


Yes, I know, but that was a transparent joke.


> Do you think Generics will be an overall win for the Go language, or will they be overused / end up making code harder to read / harder reason about?

Here's my $0.02 from a java background. There will be cases where someone overuses generics. That happen pretty much every time a new language feature lands in any language.

However, my expectation is that like java, you will see Go generics in general practice only come up when dealing with things like collections. Once the community settles and stops trying to use generics as a meta-programming language, they will become pretty boring (which is what you want).

From a readability standpoint, IMO, generics in Java don't really have a negative impact on readability. Sure, you'll get the random `Map<Map<String, Object>, Object>`... but most people see that as the anti-pattern it is.

In short, I'm guessing they'll be a win.


yeah - generics can get fairly complex for library authors that want to be maximally flexible (which is optional!), but the end result generally tends to be a library that users can use correctly by accident, without needing to understand generics in depth.

It's that "correct by accident" part that's hard to have both safe and performant without generics.


I have never needed generics in Go, and I've probably been using it since 2017. I've never even once had to resort to any interface{} trickery to express what I want, and I've written Go programs for Fortune 50 companies, as well as complex personal projects such as AST parsers/code generators.

I'm pretty disappointed to see generics introduced into the language and every example I've seen feels completely unreadable to me compared to pre-generics implementations.

To be clear, it has never been the case that the Golang authors were 100% against generics. It has always been their position that the implementation needed to be good enough to make the trade-offs worthwhile. I just don't think they chose the right trade-offs.


> I've never even once had to resort to any interface{} trickery to express what I want, and I've written Go programs for Fortune 50 companies, as well as complex personal projects such as AST parsers/code generators.

That's pretty surprising to me. Have you never had to implement marshalers for unknown types and such? I have had to implement things like json.Marshal and json.Unmarshal for different encodings dozens of times in my Go tenure. I have had to use reflection a lot. I have had to deserialize into map[string]interface{} to handle ambiguous situations at runtime a lot. Have you never even had to wrap or build your own Printf equivalents that accept interface{}? No loggers? No custom containers? None of that which operates on unknown types?

I see use of interface{} all over the vast majority of Go projects. I think your experience may be atypical.


It's entirely possible that, through some strange quirks of circumstance, I've managed to avoid every problem space that would make me wish for generics.

In spite of that, it's unlikely that I've written implementations where using interface{} would be easier to read and reason about than not using interface{}. And the experience of the author whose blog post we're commenting on tracks with mine: "In my 5+ years working in Go, I can probably count on one hand the number of times that I felt like I really needed generics." I can too, just without using any fingers :-)


I feel the similar way, though I wouldn't be so brave to say I didn't ever use interface{}. I think we all work around slices and maps being the only generic containers and don't know we do. I think everyone will find that while they didn't need generics, they will help them when using utility libraries. Java 1.4 people thought the same.

I expect well-curated libraries to come about that will really simplify some otherwise difficult problems for people (e.g. task/object pooling). I'm even toying with a futures impl at https://github.com/cretz/fut, but I wouldn't use it in place of channels in most cases.


Yes. It would be short-sighted to dismiss them out of hand, and it's possible that generics will provide a level of expressiveness and readability I haven't anticipated yet. I'm fairly bearish on it for now.


> I have had to deserialize into map[string]interface{} to handle ambiguous situations at runtime a lot.

Something I worry about is if I'm getting too jaded. You really think things are different elsewhere, but then you see that we all eat the same shit sandwhich.

That code would never have to be written if someone just used their brain before switching their integer IDs to string GUIDs. Bless your soul but I wish we didn't have to resort to such things. Some things code can't fix.


His experience coincides with my own having solved many business level problems using golang for several years.

At most I have used non-empty interfaces to solve very few number of issues (countable on one hand). I have never needed interface{}.


> as well as complex personal projects such as AST parsers/code generators.

Funny you say that because for me that's the one use case I have for generics.

(edit: why the hell am I getting downvoted for posting a fact? I wasn't offensive, argumentative, etc. Just citing one example I've run into where generics would help me personally)


There are areas where they will help, and be pretty transparent to the user, lots of places in the stdlib, one trivial example: math.Max(a,b) could take any numeric instead of just float64, sort could be neater etc.

Perhaps a Go 2 if they ever get there could be a generic std lib rewrite, largely transparent to the end user, with some minor incompatibilities allowed and lots of stuff rewritten behind the scenes. They could remove a few ugly corners in the stdlib naming specific types by using generics, add a few more container types perhaps, deprecate some old stuff and move it out.


I'm hoping Go 2 replaces the file struct with an interface too. That's been a particular pain point for me.



Sorry I should have been more specific. I mean the os file struct https://pkg.go.dev/os#File

The os package makes use of a *File struct rather than an interface. The authors acknowledged this was a mistake but it's some of the oldest code in Go and Go's backwards compatibility guarantee has meant that they cannot fix that.

Since I author a $SHELL written in Go, being able to add in my own *File methods would have allowed me to add in some cool features. But I've found workarounds in most cases. It's just not as clean code as it could have been.


What I am saying is, there is already an interface, which os.File should be implementing.


And what I'm saying is that interface was created after os.File was created and after go 1.0 was released thus that change now cannot happen without breaking the backward compatibility guarantee. Hence my point about go v2.0


never needed min/max ?


Would you use generics to write the same implementation of min/max for integers and floats?



This is a joke, right? Your trivial implementation isn't even correct.


https://gotipplay.golang.org/p/N2v8aB1tUtN

Is this one better? It does run and doesn't rely on casting (not sure why GP's source took that route, it was lossy and kind of dumb). I suspect the compilation problem was because of a change in the syntax between when that was created and today.


yeah, it's just old. the `~int | ~float32 | etc` syntax is relatively recent.


Why not? You later seem to think that this would require reflection - but that makes it apparent you don't understand how generics work in a language. They're used at compile time - to avoid runtime checking.


You certainly might use them to write a facade which made life easier for consumers of the math pkg and accept floats,ints or uints even if behind the scenes it splits into different implementations.


I would not be very happy if every time I called math.Min, I was also, under the hood, calling reflect.TypeOf.


The whole point of generics is to avoid this - why do you think you would have to use reflection?


The fact that you would not need a separate one for each of the: uint8 , uint16 , uint32 , uint64 , int8 , int16 , int32, int64 is not enough ?


As someone who's been writing Go full-time for only about 8 months now, I've repeatedly been frustrated with the lack of generics while building web APIs. For example, the Go GraphQL ecosystem is a bit of a disaster full of type unsafe code and use of reflection or code generation to support simple things like "a resolver that returns FooResult" vs. "a resolver that returns BarResult."

Here's a fun one I stumbled on: How do you implement a PUT endpoint where a missing JSON value is treated different than a null JSON value? This ends up being very difficult and requires a boilerplate wrapper type for every single type you might accept. It's even worse when you start accepting slices or maps, or slices of maps...

These are areas where generics will help me a lot.


This is because JSON and GraphQL are hot garbage. You may as well have written:

> How do I disable static typing for this statically typed language?

I have sympathy for your struggles -- I've been there. But fundamentally this always ends up being a problem of putting a square peg into a round hole.

I don't expect most folks to agree with this take, but I have the utmost faith it'll age well.

If you don't believe me now, set a reminder for ten years and see how we feel about JSON and GraphQL.


You're missing the point: even if JSON and GraphQL were better, untrusted data is always "untyped". Something has to parse the raw representation until proper data types the type system can understand.

Ideally this is all transparent, and programmers can stop wasting their lives reimplementing this stuff again and again and again, but even if programmers don't waste their time reimplementing it, computers will spend a decent amount of time running it, at least where there is more than one process / machine / whatever in question and therefore untrusted boundaries.


I don't follow. Writing a parser doesn't require generics. I've written hundreds of parsers. You can even use a generator like protoc to provide you read/write code that returns/uses concrete, static types.

What on earth does dealing with untrusted inputs have to do with anything?


I wasn't primary talking about generics, I would responding to

> You may as well have written:

> > How do I disable static typing for this statically typed language?

and so just talking about parsing and static type checking.

As it turns out, generics do help immensely if one wants to use so-called "parser combinators".


GraphQL is typed.


> How do you implement a PUT endpoint where a missing JSON value is treated different than a null JSON value?

Tbf that’s a pain in the ass everywhere unless you’re reifying it as a map (so manipulating a json dom).

Iirc in rust the “complete” way to do this for a struct (as opposed to a map) with serde require two options and a bespoke deserializer.


It's much easier to do in dynamically typed languages or by using maps - agreed. Unfortunately we have other design decisions that force us into using structs for deserialization on this endpoint (part of our validation strategy.)

The Go answer is a struct that contains an IsDefined boolean (i.e. your first option), and a pointer-to-value (i.e. your second option.)

This is fine if you need it, but having to write this same logic over and over again for every type gets old... especially if your validators are tied to your types (i.e. a type per field.)


> Unfortunately we have other design decisions that force us into using structs for deserialization on this endpoint (part of our validation strategy.)

Oh I’m not blaming you, sorry if it came across that way. I’m aware of the issue because i’ve been hit by the exact same (hence having been made aware of the rust workaround), can’t say I was a happy camper.


> Is it a better bet to move on and go full Rust

If you don't like generics you shouldn't use Rust. You can't escape them in Rust. The designers repeated the mistake of C++ and made it a language feature kitchen sink. It's an unholy mess. You'll find yourself constantly fighting the borrow checker. Type signatures are littered with lifetime annotations. The type system is Turing complete because they didn't analyze it before implementing it. Go's generics were formally validated [1]. Rust's compile time is slow, and the 'async' story is sad. Async functions are colored and infect everything.

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


>You'll find yourself constantly fighting the borrow checker.

In the beginning, yes, this is true. But most people learn within a month or two which design patterns lead to problems with the borrow checker and which work smoothly, and often this knowledge translates to good design in languages like C and C++ as well.

If you're fighting the borrow checker in Rust, you'd probably have been fighting segfaults and use-after-free in C / C++. I'd rather spend 30 minutes fighting the borrow checker than spend 4 hours digging around in Valgrind.

> Type signatures are littered with lifetime annotations.

You cannot avoid the concept of lifetimes, without a garbage collector. If you don't want garbage collection, you have to deal with them.

Having explicit lifetime annotations in the code is _vastly_ better than trying to track the lifetimes in your head from scratch every time.


> If you're fighting the borrow checker in Rust, you'd probably have been fighting segfaults and use-after-free in C / C++.

That is in my ( admittedly limited) experience just not true. There's plenty of things that are perfectly safe that the borrow checker just doesn't understand.

The borrow checker can prove that a subset of things is safe. But the borrow checker being unable to prove something doesn't mean it's not safe.


This, one thousands times.

The borrow checker forces you to write in the very narrow subset of code paradigms it can understand. When it fails to compile, it doesn't mean it's wrong: it means that it can't prove that it's correct, which is a completely different statement.


Ah yeah, I am not sure everybody know me what “colored” means but I remember when comparing a C# solution using async with a Go solution using channels and Go routines I finally understood why people keep raving about concurrency in Go. It composed very nicely while any language following the popular async/await approach turns into a total mess. I guess async/await looked good years ago because we compared it with managing POSIX threads manually. That sucked.


It's worth noting that Rust's Async functionality starts with very different priorities from Go's goroutines. Rust, as a systems language, made lack of overhead (allocations, etc) the highest priority. It's part of Rust's overall zero-cost abstractions ethos. Goroutines are just never going to be zero cost. Go chose to prioritize the interface to the programmer, which is much more a part of Go's ethos around being simple.

There's nothing wrong with either approach, they just have different trade-offs because their goals are different. Rust's approach will sometimes push complexity onto the programmer to handle. But it can be made to perform better and more predictably than the Go equivalent. This might not matter if you're not pushing the performance envelope, but if you are, Rust makes that possible in a way that Go simply doesn't. You'd never want to write code using goroutines for an embedded device with limited CPU/memory, but Rust's async is already proving useful for these sorts of projects.

However if you can tolerate the performance overhead that Go imposes, giving the programmer a simpler mental model can easily be worthwhile. Technology is all about trade-offs and you have to choose the right tool for the job.


Async/await was intentionally chosen by a lot of languages well after Go had become popular. Rust once had Go-style concurrency and abandoned it in favor of its current model.


Paraphrasing Stroustrup, there are only two types of languages: those (that end up) with a Turing complete type system and those that nobody use.


A type system being turing complete really isn't a problem. You bound the recursion depth in practice, and the chance of a real world programming hitting that limit is minuscule. Lot's of other languages have turing complete type systems, subtyping and typeclasses lead towards it.


Sure, if you regularly want to increase your #![recursion_limit] or #![type_length_limit] for the next generation of type bloat.


Hmmm I guess it is all in the eye of the beholder. If you are the kind of person who thinks C++ went wrong with its template system, then you might find issue with any language emulating C++ failures.

If you think C++ is a beautiful well deigned language, then I am sure you will not have issues with Rust either.


Java & Haskell both have turing complete type systems as well. I'm not a particular fan of C++'s type system, doesn't mean have metaprogramming features is bad.


C++'s templates go too far — way further than Rust's generics go. Rust's type system may technically be Turning-complete, but nobody's actually doing serious* metaprogramming in it, unlike in C++, where template metaprogramming is a whole discipline unto itself.

* not just toy examples


Right, because Rust provides a real macro system for metaprogramming.


I'm concerned about two things (that I hope I'm wrong about): (1) developers will overuse generics and use poor types or `any` where not necessary, and (2) this marks the beginning of Go's convergence to yet another language with a million choices for abstractions and a million ways to misuse its features.


I think they did this really quite elegantly by extending the interfaces abstraction.

Personally I would discourage overuse of generics in an application codebase as I’d discourage overuse of interfaces, concurrency or channels - they have their place in certain areas (for generics e.g. collections, orms - mostly in library code) but most of the time simply aren’t required.


Not a Go programmer but I can say that Generics were important for me to consider adoption of Go. Now that they are present, it's a legitimate language. Whether they make code messy really depends on how they are being used.


It's been a legit language for years. Frankly I've enjoyed the lack of generics acting as a filter function for astronaut engineering.

I don't want to read through a heavily templated, generic code base when concrete, simple types will do. Go is so easy to read and reason about that I'm genuinely afraid of any change which could affect that.

The best advertisement for avoiding generics in Go despite their availability is the fact that they won't appear in the standard library for some time, and that the language maintainers believe it will take years to understand how to use them appropriately.


I'd wait until we see how the updated standard library looks like. In Java they had to invent Streams. Scala/Kotlin didn't repeat that mistake and so their collection APIs are more natural to work with. Historically golang has repeated most Java mistakes so I don't hold my breadth.

On the positive side, now Either/Try-like composable types are possible. So even if they insist on not having exceptions they could clean up error processing.


> Now that they are present, it's a legitimate language.

I second that sentiment, but I would have to look into the specifics. Generics ala Java aren't really that attractive when Rust mainstreams ML-style polymorphism with Haskell-style overloading.


What about `if err return err` everywhere?


I like them personally. It is easy to ignore them, and if you want them, they are there.


I share your worries, but I think it will be just fine. There are two reasons I have for that:

First, the Go community mainly comprises people who love simplicity and got accustomed to it. I imagine most people who want to go overboard with generics will stay with languages that let them go way more overboard.

Second, and more importantly, there's no method parameterization, which saves us from monadland.


In my opinion, there are plenty of good alternatives to Go such as Nim, or Python if performance allows, and even languages like Zig, V, CommonLisp, and D depending on the use case. I don't get why people keep mentioning Rust in threads about Go. It's perhaps a replacement for C, C++ and Ada - though for the latter only if you're okay with switching from self-documenting code to unreadable gibberish. Rust's philosophy is pretty much the opposite of that of Go, and it is neither designed nor suitable as a high productivity, easy to use language.

IMHO Go generics are simple and useful, particularly for container libraries. They are fairly readable, unlike template programming and macros in other languages. Together with the any type alias for interface{} they will make code more readable.

I do hope that Go stays at version 1, though, or that it at least takes a long time to add new substantial features and get version 2. Slow change is one of the many advantages of Go and I'd rather see them improve the compiler in hidden ways.


I'm not so sure it will be an overall win.

Will it be nice with generics for carefully crafted and cherry-picked use-cases? Absolutely.

Will it make the average large codebase less readable after a dozen coders have been doing their own cleverness with generics 3-5 years down the line? In my experience, most definitely.

Of those two, I'm much more afraid of working with the latter than missing out on the former.

It sometimes feel like we as devs have a habit of optimizing for the individual dev's convenience (not to mention what's fun) rather than for the collective effort's best interest - like the long-term maintainability/coherency of the code base. To some extent I assume that's just being human, but in other professions I think it would be seen as a bit lazy and thus more stigmatized.


having played with generics over the past few weeks: my guess is that there will be a period of widespread misuse, the first six months or so after 1.18 is released. So... between February and August of next year I'm expecting a lot of the Go discourse to be characterized by a very poor signal to noise ratio. I think there's virtually no chance that Go 1.18 will come out and people will use generics well; people will use generics poorly before they use them well.

A bunch of people will write libraries that utilize generics in some way that's not very orthogonal to the rest of the Go ecosystem. They'll put these libraries out as quickly as possible, because they want a first-mover advantage in picking up adoption. A handful of people will take their time and learn how to utilize generics in a way that is native to Go. Eventually those people come out of the woodwork, and it turns out that utilizing generics in Go looks different than it looked in some other language, so a lot of the early assumptions about how to use them were wrong. A bunch of those early libraries turned out to be badly designed once actually deployed into the real world, so they fade out of use as their problem domains get written by newer libraries that utilize generics in a manner more orthogonal to the rest of the Go ecosystem. A few of those libraries picked up significant market share, and maybe a startup has shipped stuff that generates revenue with those libraries, so they fund those projects, which continue to exist as a result of pure inertia. So now you have some big libraries written in like ... March of 2022, which are just bad, but people keep promoting them because they have a vested interest in doing so, but the ecosystem at large moves on, and by late 2022, generics will fit in very nicely with Go and not complicate things in an unnatural way. I'm wary of what's coming in the short term, but optimistic of what's coming in the long term as a result of this change.


I'm certainly not a fan of Go, but I suspect generics will result in the language and ecosystem getting better. I'm not personally a fan of the go generics syntax because I find braces and parentheses to be visually similar, but I'm sure that would go away if I used it more.

I'd go for Rust tbh. I think it's a much more coherent language.


Wrote Go code daily for quite some time, also tried Rust.

I like Go because of the tooling and simplicity of the language, it's easy to learn and explain.

Rust has the borrow checker has a corner stone. The concept mutability and references is easier to understand if you are coming from the c/c++ side of things


I use Go because it compiles quickly, the GC is reasonable, the ecosystem is good enough, and it's not rocket science (Scala/Rust/etc).

While generics moves Go in the direction of rocket science, it feels like this is solving a problem I always have.


> Is it a better bet to move on and go full Rust, rather than bother with wherever the goggle golang train is headed?

If you want to unnecessarily encumber your mind with trivial but tedious memory management puzzles, then choose Rust.


code might be harder to read but easier to interact with, because the type is more correct. That's my experience with typescript. Generics in typescript is used way too much comparing to other mainstream languages because it's way too powerful comparing to them and I doubt golang will ever go there. However, I think people focus too much on readability often in the sense that's code is a static thing. In reality, we interact with code and poke around and use type inference to discover them.


I hope that this quote from the article is true:

> I’m not sure that most Go developers will be using generics daily, but it’s nice to know that they exist if we need them.

Most people won't need generics in Go, and I hope people don't force them into their code where an interface would do just fine. I'm a big fan of good type systems like Rust, but Go doesn't need all that power all over the place. I think generics are a good feature for Go, but I really hope they don't get overused in places that would currently use an interface.


My hunch is that Go packages will still work fairly well for isolating gory details. The guts of a package can get more complicated with generics but there is friction for consuming anything more clever than, like, ‘lists.NewLinkedList[myType]’ outside; further, the path towards cleverer and gorier cross-package APIs mostly goes through interfaces. In this way, underneath some new syntax, the concepts employed when using polymorphic type parameters largely resemble the status quo for dynamic dispatch.


Every language feature gets misused in some way, but if it is overwhelmingly used to clarify and reduce code, I think it is a win. I believe generics will overwhelmingly be used to do this, so I think they are a win.

I doubt more complex features than this will be added, considering how long it took to get generics.

Rust is a very different language from Go, though it can do similar things. If you have people willing to learn it, you can certainly try, though you might find the ecosystem lacking.


Hum, I've never seen generics making code harder to reason about in any language, except, of course for C++, where they are hacked over text.

If they will make code harder to read, that's up to syntax. I don't know how all that will look up on the end, but it should be reasonably easy to just write an example.


> Hum, I've never seen generics making code harder to reason about in any language, except, of course for C++, where they are hacked over text.

do you mean templates?

i was under the impression that c++ generics == templates, but after a google search found out that c++ has both (at least according to microsoft), not surprised

https://docs.microsoft.com/en-us/cpp/extensions/generics-and...


C++/CLI is C++ compiled to NET CLR IL, so it has all the features of the regular C++ compiler (templates) AND all the features of the CLR (generics).

It is an extremely niche language, extremely rarely used even in the .NET ecosystem, except sometimes as glue code.

Normal C++, including MSVC C++, has only templates.


It is definitely nicer than trying to correctly get P/Invoke declarations or debug COM marshaling issues.

This is the kind of tooling that makes me still reach out for C++ when going outside managed languages.


My experience was that P/Invoke is much easier, but of course YMMV. COM I never played around with.

I also found a pretty ugly bug in the C++/CLR compiler - if you used in-place initialization for an array (something like auto arr = Object[]{obj1}), it would allocate an array of length 0xC0FFEE and set the elements you specified. They acknowledged the bug but said they will only fix it in a future version of the language.

This told me all I needed to know about how popular it actually was.


It is popular enough for being one of the major milestones on .NET Core 3.1.

P/Invoke can only do so much if a C++ library doesn't provide a C ABI, and WinDev loves to publish COM based APIs since they won the Longhorn dispute.


Yes, generics in C++ are created with templates.


I have never felt the need for generics but many of my friends complains about the lack of it in Go. The syntax seems to be as readable as any other lang with it.

Go is fairly dominant in some areas and Rust is not really a realistic replacement today. Generics will not change that.


>I kind of hate looking at Go code containing generics.

Perhaps that will change as you get more used to it?

And for code you consume (e.g. using a library) Generics will make your life easier and safer.


> I kind of hate looking at Go code containing generics.

Pretty much any mainstream language that supports generics is shit to read. (And I love to read code.)


While generics are definitely useful to implement container types, it seems that the Go design seems to prevent that. It seems to me that generics get overused when the language allows you operator overloading for example. Go is a "one way to do thing X" language, it doesn't give much room to write "creative" code


> What previously was elegant and easy on the eyes is now annoying and muddled by the additional layer of abstraction

You're just getting old is all.

What was once familiar now isn't ... how annoying.

If that worries you, don't: we're all walking down that path.

Give it a couple of decades and everything in your life will feel like that.




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

Search: