Hacker News new | ask | show | jobs
by pstuart 1 day ago
Sounds like a win to me. I'd assume that many PL decisions come with with costs of either implementation or readability, and Go's conservative evolution takes that into account. Disclaimer: not a PL designer, just a codemonkey.

It's not a perfect language, and the process has not been without its pain points, but work like this makes the language more powerful and I feel like I get my money's worth when I use it.

Yes, I'm a Go fanboy but I'm not interested in language wars (e.g., yes Rust is more performant and correct but sometimes "good enough" is good enough).

2 comments

On the other hand, design decisions made under one set of constraints can become problematic when you add new features that don't play as well with earlier design decisions.

For a concrete example, the fact you cannot define custom methods for external types in Go (a pre-1.0 design decision) means that you cannot write proper compile-time generic code to deal with some generic wrapping type (dumb example -- getting a sum of the perimeters of a generic set of shapes in an externally-defined collection type) -- you are forced to work around it with runtime type-switching. There are all sorts of arguments you can make about simplicity but this is an objective shortcoming of Go that is directly caused by generics being added to Go long post 1.0.

My take on this is that despite selfishly wanting more from Go for many years myself, adding more stuff to Go at this late stage is just slowly chipping away at Go's uniqueness with features that make a large number of people using them unhappy. (For example, while they make some APIs nicer, iterators turn a basic logic bug that is impossible with for loops -- forgetting to stop iterating when the loop has a "break" -- into a runtime panic. And they really suck to compose.)

__For a concrete example, the fact you cannot define custom methods for external types in Go (a pre-1.0 design decision) means that you cannot write proper compile-time generic code to deal with some generic wrapping type (dumb example -- getting a sum of the perimeters of a generic set of shapes in an externally-defined collection type) -- you are forced to work around it with runtime type-switching. There are all sorts of arguments you can make about simplicity but this is an objective shortcoming of Go that is directly caused by generics being added to Go long post 1.0.__

I'm a bit intrigued by this: how would that work with compatibility between libraries, separate compilation and what not? My first instinct is that it would not be a good idea but I might be missing something..?

In Rust this is solved by defining a local trait and methods implemented for a trait are only available if the trait is imported. This is in contrast to Go where methods are not namespaced at all. But there are almost certainly many other ways of solving the problem.

I was a little surprised how often I ran into this issue when using libraries that started defining structures as generic containers.

Yes, so that is exactly what I don't understand, that makes compilation flow sensitive in a way that Go avoids by design in order to remain fast.
Not really, Go already disallows import loops so even if Go did have exportable traits the compilation flow would be the same.

But traits wouldn't really make sense for Go -- my point was that there is are solutions for this problem in other languages; Go's original design goals don't gel well with generics which leads to these kinds of issues. They felt generics weren't necessary so they didn't design Go with them in mind.

> the fact you cannot define custom methods for external types in Go

Just wrap them. The external type is theirs. Make your own wrapper and do whatever you like with it.

People want every language to adapt to them, instead of adapting themselves to the language.

That is the classic answer for how to do that in pre-generics Go. It doesn't solve the problem I mentioned.
> (dumb example -- getting a sum of the perimeters of a generic set of shapes in an externally-defined collection type)

There's a much more common example: dealing with external APIs that return JSON. Their response is trivially wrapped in Req<T>. Pre-generics Go would force you to write tedious duplicated boilerplate code for each request type, or just "cast to void*" with interface{} and hope for the best.

> adding more stuff to Go at this late stage is just slowly chipping away at Go's uniqueness

Uniqueness should not be the goal of a language.

Fair enough. Primeagen did a video recently renouncing his love of Go over the concerns you've raised.

That said, I'm assuming much of this "wobbly" functionality will live in libraries and will not be common in day to day coding (much as I've seen with generics so far). It's all optional after all.

I use Go because it's simple, capable, and been my prime language for over a decade and that skill enables me to be valuable enough for a decently paying job.

If I were younger with more energy and time on my hands I'd likely be a rustacean but I think I can ride out my career on this. YMMV.

I've maintained one of the major container runtimes (which is written in Go) for over a decade as well, I think I have a less rosy view of Go than you but I still think it's a nice language all things considered and still use it for some projects.
My goal is to be pragmatic about the issue (and everything else).

When I started with it I saw it as the love-child of C and python. Then saw it as a successor of Java.

I recognize that Rust will win this contest (although my understanding is that async still has sharp edges to round down). I'm old and tired and have lost the energy and focus for exploring new languages and believe that despite its shortcomings it will continue to have enough value to be useful.

Good enough will do for me. I tip my hat to the all the other PL contenders and wish them all well (except Java -- PTSD from that destroyed any love I had for the language).

Rust will not "win" when it targets a much different niche, any more than C won against Java or Scheme.
I see a lot of overlap between Go and Rust -- with Go's "advantages" being simpler code to reason about and more ergonomic concurrency.

As Rust and its ecosystem mature and LLMs get better and cheaper, I imagine we'll see more ports of Go projects to Rust.

I love Go and admire Rust but I didn't have the energy to get through the learning curve, and Go does enough for me for what I need so I'll be ok if I never properly use or master Rust.

An ex-boss loathed Go and cited "but the nil pointers!" as his reasoning for his contempt and made sure to migrate the flagship project written in Go to Rust. People get emotional over languages but they're just tools and not worth fighting over.

"Everything is a trade-off" assumes you're on the Pareto frontier.

Go is... very much not on the Pareto frontier of programming language design.

> "Everything is a trade-off" assumes you're on the Pareto frontier.

100%. it pisses me off so much that i have to hear this idea thrown around constantly.

Somewhat. The trade off is it can be made more complex at the cost of its simplicity. You can argue that some of those tradeoffs don't in fact have to be made because it's not on the Pareto frontier, eg generics, but we'd have to have a specific discussion about specific language features instead of vague generic discission about the language.
I find language wars tiresome at this point. There was no claim that it was a frontier language, or even that it was a great language. My point was that the Go authors do try to consider the impact of enhancements to the language, which I think is appreciated by many that use it.