As a person who was initially disappointed by Go and who has come to love it, one of the biggest things I learned from watching it grow was the following:
As far as languages go, Go is relatively well-funded. You can of course imagine hypotheticals where there was more money behind it, but on the spectrum of PLs it's got more support than most. And further, I think the core developers who work on it like rsc and bradfitz are objectively great implementers. Despite that, the majority of complaints about Go are a lack of language features; you even see some in this thread.
In other words, a language with an above-average amount of effort and ability and 14 years behind it is still perceived as feature poor. But imagine how much more time it would have taken if they had taken on a larger scope, like if they had gone through all the complex design process for generics up front.
For me it is a great lesson on how important reducing scope is for shipping, and also how a reduced scope enables you to more judiciously choose what is important. If they had decided that features X, Y, Z were table stakes for their launch they would never have had the time to do the other things that I think have made Go great.
I only have a small amount of Go experience but in my humble opinion I don't think the lack of features is due to lack of time or funding. In fact it would have probably been much easier to say yes to a bunch of features than to continually deflect and reject the demand for those. I'm sure anyone working with different stakeholders recognises this effect.
They have very deliberately tried to minimise the feature set in order to keep the language easy to learn. C++ is probably the canonical example of a language which hasn't kept the feature set tractable but I'd even argue that Python may have fallen into this trap in recent history.
Complex language features have much more of a toll on reading code than they have on writing code. One example that I like to use is that while Go's lack of operator overloading is occasionally a nuisance, it means that you'll never have to dig deep to understand what "+" is doing on this particular line, since it'll always behave according to the core language rules.
I think the total cost of debugging hours often ends up being several magnitudes higher than the cost of development hours. At least for me, the process of debugging is something that involves scanning the code to load up my brain with as much possibly relevant context as I can and then try to analyze what parts of that might explain the seen behavior. A carefully designed language should help to both minimize the size of that context and the amount of linguistical gotchas that are part of it.
If I'd use an AI assistant, that lets me ask more pointed questions. Wouldn't that improve the assistance the AI can provide?
That is not necessarily true. Especially the error handling makes it really awkward to read go code. A lot of languages with an expressive type system do look aweful when looking at methods. But the same thing can be done with go methods. Writing hard to understand go code is just as easy as say with scala or rust
Oh, absolutely. I don't mean to claim Go as an outstanding example of a well-designed programming language. I appreciate Go's language design, but I also similarly appreciate Scala, Rust, Java, Erlang and various other programming languages I've interacted with.
My point is more against the notion that AI assistants would make careful language design any less important than in favor of any particular language. The popular programming languages all have their own virtues and drawbacks (and the unpopular ones almost certainly have at least their own drawbacks).
> Especially the error handling makes it really awkward to read go code.
Go does not have error handling at all, just general value handing. What do you find awkward about its value handlers? They are essentially the same as every other C-style language out there.
It's certainly not difficult to understand Go error handling. But it does take up quite a bit of real-estate. I think more importantly for me, it's too easy to forget an error check. Having primitives which make error checking painless and validated by the compiler would be nice.
If I do the latter, I cannot ignore `err`. If I do, the compiler yells at me because there is now an unused value in my code.
If I do the former, then I made the conscious choice that the error doesn't matter to me. Yes, this goes for `foo()` as well, because Go makes it exceedingly clear that errors are just return values, and that call ignores all returns.
I never understood this. I do understand it is easy to forget to handle an error like it is easy to forget to handle a distance or temperature. Humans will make mistakes.
But nobody laments about how they forget to handle distances or temperatures, or cry for special constructs to ensure that they don't screw up their distance and temperature handling. What is it about the word error that sends developers into a tizzy?
Why would we want a solution that only works for errors and not for values of all kinds?
Because handling errors is important for complex programs. Whether errors-as-values or exceptions or aborts are the best option, being able to write a program that handles errors well makes it that much more robust. Don't want some weird behavior happening when the program should've noticed an error and dealt with it somehow.
Handing values is important for all programs. You don't want some weird behaviour happening in any case. If you are writing a system that turns on a heater when the temperature falls below freezing, you're going to have a really bad time if you forget to handle the temperature.
So, yes, it makes sense to have constructs that can help with that problem, but it would be weird to have such constructs only for what a human considers an error condition. You would want that for every case that needs to be handled.
Otherwise you have to resort to testing, and if you accept that testing is good enough to ensure that you don't forget to check the temperature, then it also good enough to ensure that you have checked an error. There is nothing special about the error case. It's just another state like any other.
An error signals that a non-error value you were relying on will not be available. Control flow should nearly always change in response to that, often drastically. That’s why a language should provide a caller-controlled non-local exit from a futile chain of calls. Many languages only do that for errors, which is not a major limitation when users can define errors.
> An error signals that a non-error value you were relying on will not be available.
Except the non-error value should always be available, even if that means relying on the zero value. Zero values should be useful. An error state is an independent state. It should also be useful without any other values.
While the caller may choose to use the error state to make decisions about other state, it is faulty API design to force that upon the caller.
Many domains have no meaningful zero value. If “getUserByID” returns a fake user, anything you do with him will be incorrect, if you don’t panic and die (as zeroes of builtin types often do).
Logically, getUserByID returns nil when there is no user, not a fake user. The caller can then check for its nil-ness without needing to consider the error value. nil is a useful value. For better or worse, nil is how Go convention represents the absence of a value. In fact, convention sees error also rely on nil to be useful in the very same way.
It's funny how people completely forget how to write software as soon as they see the word error. Why is that?
Well, I think that's why a lot of people are disappointed that Go doesn't quite have sum types, because sum types do increase expressivity around values in general. That they improve error handling is just one application.
There seems to be no consensus as to what sum types actually means, but the most popular definition I find in my travels is: Tagged unions. Which is funny as a sum is not a union, but anyway...
Assuming you share in that definition, while Go might benefit from tagged unions in general, I'm not sure they actually help in any way with remember to handle errors. A union is just as easy to forget to handle as a single discriminate type.
But perhaps you are of another sum type school? Perhaps one of the other sum type definitions are useful here?
Right. See that doesn't help, at least not in the context of Go without any other modifications. Logically, you can reduce that Result state to a simple boolean (Ok = true, Err = false). But the bool type in Go is prone to the very same problem.
It doesn't matter how many type layers you add. The problem is that there is nothing in the language that enforces handling of values, no matter what type that value might be.
Yes, manual tagging doesn't provide type safety. That's why I said some people are disappointed that Go doesn't have sum types. There isn't even a convoluted workaround either, at least not a full one.
It is not so much a matter of lack of type safety, it is forgetting to implement an application requirement.
Even an advanced type system capable of formal proofs isn't going to help you if you've forgotten the requirement when defining the types. And if you are using a language with a lesser type system (Go, Rust, any language you are likely to use in the real word), and haven't forgotten the requirement, then you would have encoded that knowledge in tests instead, making the forgetfulness in implementation ultimately immaterial. After all, there is no practical difference between learning that you forgot to implement logic by the compiler telling you or the test suite telling you.
So, we can infer that the OP is talking about where one straight up forgot about the requirement from start to finish. I'm skeptical that there is any technical solution to that problem, but sum types most certainly isn't it.
There is a point at which even dependent typing won't save you, but sum types are a useful tool to reduce negligence. Don't let perfect be the enemy of good.
Sum types are useful tool, but they are not helpful in this particular case. There is nothing about sum types that would help avoid this problem in the slightest. Perfect need not be the enemy of good, but good is not equivalent to not even trying.
This is not a general "wouldn't it be nice of Go had..." thread. We are talking about a specific problem.
I'm not really sure what your point is then. We should have comprehensive tests and therefore error checking isn't special compared to other programming mistakes? I think we should both have tests (don't skimp on them if given time, of course) and use tools like sum types to write more robust programs from the get-go. Errors can even be treated like other values using sum types; that raises the bar universally instead of just improving error checking.
If you're going to invoke some abstract "the programmer doesn't know what they're doing" situation, I'm not sure how to continue this discussion. Even testing may not help in that case. Things are going to slip through the cracks. Having features like sum types is a way to generally reduce the likelihood of such semantic errors.
Besides, the original topic was that Go doesn't make its error handling very foolproof. Sum types help with that and don't make error handling special, so I really don't see your point. Why not write everything in Brainfuck then? Everything is uniform and simple. Well, it's simple to the point that it's way too easy to make mistakes.
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." - Brian W. Kernighan
I dont know if it applies to language features, though.
Because parent is taking about operators like "+" and how it can be confusing not knowing if a function call is behind it. This is different from obvious functions like "Add()".
Because if the language doesn't allow overloading (which is the context of this thread) then "+" will always be implemented by the language. That's a big difference.
> They're just two different names for the same thing
No, they are not. One is a function, the other is an operator.
An operator has implicit meaning. I know with absolute certainty, that `+` is supposed to mean that 2 things are added to one another.
`Add()` is a function. Its name may or may not indicate what it is actually doing. Even if it indicates it, it may not be obvious how it is doing it's thing, or how good the indication is. Example: If Add takes as first argument a list and as second argument an integer, does it append the integer to the list? Does it add it to every element of the list? Does it sum the list and add the integer to the result? All these things could be meant by "Add"-ing an integer to the list. Does it have side effects?
I don't know, and I don't expect to know, until I look at `Add()` (or at least read it's documentation).
That is the big difference between operators and functions; With operators, I expect to know what it does, without looking it up. And that's also the big problem with operator-overloading, because as soon as a language supports that, that assumption flies out the window.
> Go's lack of operator overloading is occasionally a nuisance, it means that you'll never have to dig deep to understand what "+"
It's funny, in 10 years of writing Python professionally I can only think of one occasion when I had to dig into what a __add__ operation did (a Money library which didn't special case 0 meaning sum didn't work as you might hope).
I think it's probably because python has a pretty comprehensive standard library which gives reasonably consistent examples of where to use them.
A line of code is written once but read ten times. AI making it cheap to write will only increase the maintenance burden. And if you need AI to maintain AI written code, you'll have gained nothing but a new dependency in your toolbox.
My assumption about something coded in Go is that it was expected to be persistent and actively maintained. I agree that AI is good for one-off scripting purposes.
If you can’t design your program in your head, and can’t trace the code by reading it, neither the compiler nor AI (i.e.: code derived from other people’s code) won’t help you.
I like Go in that regard. You can keep the whole feature set in your head, and read/simulate the code without getting confused, and I’m telling this as a C++ fan.
The downvoting is so cute. Given the pace of AI development, y'all really think we're going to be typing individual lines of code in languages like golang? Seriously?
Yes, I do expect that, and I will continue to do so, unless someone can show me a breakthrough in language modeling, that isn't just a big-data derived stochastic parrot.
Being able to write code because I have seen lots of it, and know what code is supposed to look like, and being able to write code because I understand what code does, and what it does in context, are 2 completely different things.
I don't think the lack of features is due to time or funding constraints: all the large languages that originated after Go have more elaborate features, and many of them have less funding than I assume Google puts towards Go.
Rust, Kotlin, Swift, and Dart are the main ones I'm looking at. Rust and Dart had an official package manager long before Go did. All four have had some form of generics, at least the first three have sum types, null safety, and many other features that are missing in Go. These languages might have as much funding as Go (I don't know), but they've had less time to implement these things.
Rather than funding constraints, it seems to me that Go has avoided adding features because that's not the kind of language they want to design. It may have led to more polish than some of the languages listed above (I personally prefer programming in any of the first three to programming in Go), but it's not a given that Go would have taken longer to build if they'd made the scope bigger.
> on the spectrum of PLs it's got more support than most
Go definitely has much more funding and developer hours put into it than open-development languages like Python - that’s got to be a good thing for the language.
On the other hand, would you choose to build a long-term business on top of Go, given that all that support comes from one company? What if Google decide that maintaining Go is no longer in their best interest?
> On the other hand, would you choose to build a long-term business on top of Go
a) Absolutely and b) already done.
Go is stable. The language is battle tested. It works as advertised. It is open source. Even if google pulled all funding (unlikely, since alot of their architecture seems to use it), the language is maintainable.
And btw. Languages don't have to continuously evolve to remain viable. Modern languages seem to indicate otherwise, but that behavior is what Go actively avoids; adding everything and the kitchen sink into the language as soon as some other language has it. If Go showed one thing, it is that PLs don't need to add as many features as possible.
And just to prove that point: C is still a top rated language in terms of usage. When was it last updated? C17 came out in 2017, and did not add new features. It superseded C11, which came out 6 years before that, also mainly standardising things that were already de-factos in most compilers. Before that was C99 in 1999, and before that is only ANSI-C and K&R.
It is open source. Even if google pulled all funding … the language is maintainable.
First of all it’s not just an issue of maintaining the language itself, but all of the associated tooling as well. Second of all, it’s not just that Google could pull support, but that Google could choose to prioritize their own needs over the rest of the community.
And btw. Languages don't have to continuously evolve to remain viable. … If Go showed one thing, it is that PLs don't need to add as many features as possible.
Go advocates say this often enough that it’s honestly beginning to come off as an insecurity. And I would be concerned about cultivating a community around “less is more, Go should only evolve as a last resort”.
Moreover, keep in mind that the primary selling point of Go were goroutines. At the time Go was particularly well suited to exploit a gap left by other languages. Fast forward to today and Go has become a significantly less compelling option.
And just to prove that point: C is still a top rated language in terms of usage. When was it last updated?
Projects written in pure ISO C are relatively few and far between. In fact many high profile C projects are exclusively tied to one or two compilers. Furthermore, ISO C has delegated a lot of functionality to external standards like POSIX instead of giving them first class support, not to mention the plethora of nonstandard language extensions found in many compilers[0]. In other words, ISO C has stagnated, albeit C23 is a big update, while the C language has evolved.
Anyway, as I see it, golang is basically a dead end as a PL. It doesn’t interface well with other languages, it doesn’t substantially differentiate itself as a PL, any nuance you encounter with the language is likely to never be fixed, it’s a poor language for writing great libraries and/or abstractions, etc. That said, plenty of great businesses have been built on top of languages like PHP, so it can definitely work. Personally I don’t expect anything like another kubernetes coming out of the language again.
> First of all it’s not just an issue of maintaining the language itself, but all of the associated tooling as well.
Which is part of the language core, and distributed alongside it.
> Google could choose to prioritize their own needs over the rest of the community.
Which they would do why, and how specifically? This isn't a server or a database, it's a PL. What specific scenario do you see for such a change? And even in that unlikely scenario, the language is open source, and the community can fork it.
> Go advocates say this often enough that it’s honestly beginning to come off as an insecurity.
How anyone choses to interpret this message, is their business. We say it because it's true.
> And I would be concerned about cultivating a community around “less is more
Why? It seems to work exceedingly well. And there are more than enough languages following the "everything-and-the-kitchen-sink" approach already. What benefit would there be in trying to secure a tiny portion of a niche that is already overfilled?
> that the primary selling point of Go were goroutines
They were one of the selling points. What anyone sees as "the primary" point is a matter of opinion. For many people, the primary selling point of Go is the simplicity of the language.
> Fast forward to today and Go has become a significantly less compelling option.
Really? What other mainstream language uses CSPs with the same syntactic ease and core-language support?
> Anyway, as I see it, golang is basically a dead end as a PL.
What data specifically supports that point of view? Because, going by usage metrics, like code contributed, Go is easily among the most used languages, and still growing.
If you have different metrics, please, do share them here.
There's a lot of misinformation, bad arguments and bad conclusions in this post. Let's pick it apart.
> Go is stable.
But, past isn't a guarantee of the future. It was stable before, but who's to say it will be in the future? The fact that it's "owned" by a single entity should make you scared that it wont, because the whim of that entity is what can change any property of the language.
Additionally, Go has no standard, no alternative implementations to speak of. All of this should have you concerned for the future (but you seem to ignore the obvious dangers).
> It works as advertised.
It's the opposite. Languages with single (or one primary) implementation advertise how they work. Go is implement first document later language. The public discussion is for show because public doesn't control how things are being implemented. The Go dev. team may listen to everyone's opinion, nod in agreement, and do something else entirely, and nobody will hold them responsible for the discrepancy.
> the language is maintainable.
By who? How did you come to this conclusion? There's only evidence to the contrary of your argument. There isn't any other organization that's interested in maintaining Go if Google were to go away. At least nobody expressed such an intention publicly.
> Languages don't have to continuously evolve to remain viable.
This is demonstrably false. Hardware changes, operating system interfaces change. If you don't keep up with these changes, eventually your language becomes irrelevant. To further illustrate this point: today, versions of Python 3.7 and older cannot be built from source on modern Linux with current GCC. Python's dependencies on libffi and libssl are incompatible with their current interfaces, and so the language can only be built with older libraries (but then the operating system needs to use the older ones too) or statically linked with those libraries, but in case of libffi that doesn't help because it will not work for its intended goal. And the further back you go, the less likely it is that you will be able to build and use any version of Python. Bet, the same is true for Go.
> When was it last updated? C17 came out in 2017
C standards don't get implemented on the year they are released. Bet you Microsoft probably doesn't fully implement this standard. (I don't know, it's a guess based on past experience using their tools).
In more broader terms, I have no idea why did you bring C into this argument. C is an entirely different language in terms of governance, support, standardization... It's basically all things that Go isn't. Standards and multiple interested vendors ensure the survival of the language. The whole argument against using Go for long-term projects is that it's not like C. You trying to point out that C is used successfully for long-term projects doesn't help to show that Go would too. If anything, you should suspect that it won't, because Go is the opposite of C.
Can you please not post in the flamewar style to HN, regardless of how wrong someone else is or you feel they are? It destroys the curious, thoughtful conversation that we want here, and you can make your substantive points without it.
> This is demonstrably false. Hardware changes, operating system interfaces change. If you don't keep up with these changes, eventually your language becomes irrelevant. To further illustrate this point: today, versions of Python 3.7 and older cannot be built from source on modern Linux with current GCC. Python's dependencies on libffi and libssl are incompatible with their current interfaces, and so the language can only be built with older libraries (but then the operating system needs to use the older ones too) or statically linked with those libraries, but in case of libffi that doesn't help because it will not work for its intended goal. And the further back you go, the less likely it is that you will be able to build and use any version of Python. Bet, the same is true for Go.
If Python 3.8+ didn't exist, how difficult would it be to update cypthon to target newer GCC? Correct me if I'm wrong, but my sense is this would be a relatively minor maintenance task.
I suspect no one has updated Python 3.7 because there is no need (and because the Python foundation would prefer to nudge everyone to update anyway).
>There's a lot of misinformation, bad arguments and bad conclusions in this post. Let's pick it apart.
No, there really isn't, but I had fun answering :-)
> But, past isn't a guarantee of the future. It was stable before, but who's to say it will be in the future?
Whos to say C will be stable tomorrow? Well, the fact that things like GCC are standards unto themself, even without any official governance (Which C has in addition). Its the same for Go.
If anyone was to change that, all I have to do is check out an earlier version of this open source language, and use that. And since tons of code rely on this, that is what would happen.
Languages don't become unstable because they suddenly change trajectory. This rarely ever happens. Language instability requiring constant evolvement and developer effort (both from the languages devs, as well as the languages users) is the result of feature upon feature getting heaved upon a language, along with codebases relying on these features, necessitating constant maintenance effort to keep up to date.
Go, explicitly, has a completely different design trajectory. And as a result, Go code that was written in Go 1.8 will still compile today.
Btw. if you look at the listing, MOST languages, including commonly used ones, don't have an international or national standard. Many don't even have a de-facto standard. Among them are many tried and battle tested languages.
> and nobody will hold them responsible for the discrepancy.
Anyone unhappy with the implementation is free to fork the project and take it in a different direction. He who writes the code makes the rules. If people are unhappy with that, they can fork, or use another language. And people seem to be very happy with the language: https://madnight.github.io/githut/#/pull_requests/2023/3
> By who? How did you come to this conclusion? There's only evidence to the contrary of your argument.
What evidence is there for the assumption that Go would vanish if Google lost interest?
> This is demonstrably false.
No, it is not, as demonstrated by the example I gave regarding C. The language didn't change much from C99, which itself wasn't that big a step away from ANSI-C. C99 was a quarter century ago, and C remains one of the most used languages in existence.
> To further illustrate this point: today, versions of Python
I am pretty sure I never used Python as an example for this. If you disagree, quote where I did.
> In more broader terms, I have no idea why did you bring C into this argument.
For a very simple reason: To show that a language that is mostly feature-freezed, and so stable that I can run a modern compiler on decades-old unchanged code, and still get a runnable executable, can be, and are, incredibly successful. Go has been called "C for the 21st century", and for everything other than System-Programming, that statement holds true.
> The language didn't change much from C99, which itself wasn't that big a step away from ANSI-C. C99 was a quarter century ago, and C remains one of the most used languages in existence.
The library and compiler specific extensions did.
Besides, a language that nowadays only matters on UNIX clones and embedded development, naturally doesn't need to keep changing all the time, anyway C23 is around the corner.
That's true, but Go's backwards compatibility is a side-effect of another design goal, one that many contemporary languages do not share: Keeping the language small and stable, and actively trying not to add shiny new features.
Many languages have pretty good or even excellent backwarsds compatibility. In languages that incude as many features as possible, that quickly becomes a burden that requires it's own fair share of developer attention.
Without any hesitation. Google won't kill Go given how much they use it internally. Even if they do, someone else will take over. Additional benefit of choosing Go over Python is lack of breaking changes in minor releases.
What if Google goes under? What if the next big lawsuit against Google makes it disown portions of its business, and they say goodby to Go as a result? (That actually happened to IBM for example, at the time they were virtually the sole provider of all software services in the world -- that's more or less why and how we have software industry today).
What makes you so convinced "someone" will take over? Is there anyone who offered themselves in such capacity? I've heard a lot of people making similar bold claims about Python 2.X -- sure, someone will step in and maintain Python 2.X indefinitely... Well, no one did.
Then Go is still an Open Source Project with massive community support, that everyoen is free to form and extend or maintain by himself.
> What makes you so convinced "someone" will take over?
Because tens of thousands of projects depend on the language? Because the people who developed go don't just vanish if Google suddenly did? Because OSS has a long history of maintenance changing hands?
> sure, someone will step in and maintain Python 2.X indefinitely... Well, no one did.
There is a simple reason for that: Py2 was obsoleted by it's successor, Py3. There is simply no point in maintaining an obsolete language, especially if it has a direct successor, that is both similar, and an improvement in every single way.
And even so, Python 2.7 was still supported for almost a decade after it's release.
You took just one example. And, although there weren't many cases of proprietary languages or almost-proprietary languages dying in recent years, there are, of course, examples of them dying. You just picked one where this didn't happen...
So, ActionScript would be one example. Dylan would be another. There are probably many more of lesser known proprietary languages that died never making the news.
And, looping back to the original subject: parent believes there's no danger in using an almost-proprietary language, where the evidence is that owners of proprietary languages do kill them every now and then. Do they do it all the time? -- well, no, but I didn't claim anything like that...
---
As to your observation about Python 2.X -- you didn't understand in what context the argument was made. C, or any other language with a standard is warranted the lifetime that's as long as humanity is able to read and understand the standard as well as implementing it. So, 20 years will be essentially insignificant compared to the longevity of a language with a published standard.
Please don't confuse this to me advocating for every language needing a standard. Writing one severely restricts what a language can offer, while at the same time, immortalizing potentially harmful features. Going back to see, we wish today that all the str* stuff would be gone, or stuff like atoi()... but that's not an option anymore, because this stuff made it into the standard. Languages w/o one can shed their skin and remove bad decisions. But, this same feature makes them less viable for long-term projects.
How is asking for examples for a statement a "terrible comment"?
>go is changing the loop semantics to scope loop variables to the loop. Now, I like and agree with this change, but it is a breaking change.
Please explain what exactly that change breaks.
> Behavior changes that are only apparent at runtime is where go makes breaking changes.
Such as? And yes, I am asking for examples. Because I have personally compiled and run code that was written in Go 1.8, with a modern compiler. And the only change I saw so far, is better performance.
> How is asking for examples for a statement a "terrible comment"?
Demanding 10 examples is a terrible comment.
It communicates hostility and disbelief rather than curiosity.
> Please explain what exactly that change breaks.
https://github.com/golang/go/issues/60078 is where this is discussed.
Again, I _agree_ with this change, but I'm pointing out that it is a breaking change. rsc points out in the proposal that it is a breaking change.
> Such as? And yes, I am asking for examples. Because I have personally compiled and run code that was written in Go 1.8, with a modern compiler. And the only change I saw so far, is better performance.
I gave multiple examples already.
If you are truly interested I'm sure you can find more either in the go issue tracker or on google.
> Demanding 10 examples is a terrible comment. It communicates hostility and disbelief rather than curiosity.
It followed the same tone of your comment, where you claimed that they were doing frequent changes, without providing any examples, of which there should be numerous, if they really occur so frequently.
We can discuss semantics here, but a breaking change, to me, is one that that breaks well-crafted existing code. Well, what kind of real world code would rely on the fact that closures over an iteration target examine a value that depends on runtime behavior instead of the actual value that, semantically, seems to have been closed over?
I have stumbled upon this issue myself when I first learned Go. I have used Go professionally and in private projects and since long before modfiles became a thing. I have NEVER seen code that relies on this completely unintuitive behavior. This is not a change that breaks something good, this is a fix that removes a footgun from the language.
That's some of the real-world evidence in favor of changing 3-clause loops.
I have looked, and I found no evidence in favor of not changing them.
If you want to make the case for not changing them, the way to do that would
be to provide real-world examples of code that breaks with the new semantics.
So, semantics aside, no, I do not consider this a breaking change, because while it is, technically, not backward compatible, it doesn't break existing code.
> I gave multiple examples already.
What exactly breaks in the archive/tar package? I looked it up, only found some bugs and issues, but nothing I would consider a change that breaks existing code.
What exactly did modfiles break? I can still compile code without modules. Yes, modern versions of the go-tool require a flag via envvars, but they are still compatible with old projects. And libraries changing is not the same as the language changing. If a build relies on a certain commit on the library, I can always check out that commit.
Go is so deeply embedded in Cloud computing that if it were abandoned it would break the internet. Some of the most critical systems are built with it.
If it’s good enough for Kubernetes then it’s good enough for any business.
I believe, Kubernetes was initially built in Java. Its codebase reeks of Java for miles even today. I'm pretty sure that porting it back to Java wouldn't be too hard, should Google discontinue Go.
Pretty sure Internet and cloud will not go anywhere if Go vanishes tomorrow. Even if you need containers, there are plenty of tools that don't use Go, and give you similar service.
I've lived enough to see technologies with humongous popularity die in a matter of years (eg. Flash). Those who didn't believe Flash would die made very similar arguments about how certain things would be "impossible" w/o it. Not sure who am I quoting here, but there's this saying that "cemeteries are full of indispensable people".
I would be more concerned that Google maintains the language because they do or intend to use it for one of their surveillance or monetization schemes.
> On the other hand, would you choose to build a long-term business on top of Go, given that all that support comes from one company?
Like Java? Or c#? Go is hardly unique in that it's developed primarily by a single entity.
...but even if Google abandoned it, it seems like you're making the assumption that no one in the world would continue developing an open source project, and the source would just become unusable, or something? It's not like Google can just turn off go lmao
As I understand it, Go is funded by Google in the sense that core language development is largely undertaken by Google employees, not in the sense that an independent language foundation organization that receives funding from external backers invests in core language development.
Yeah definitely, starting the project in 2007 to releasing publicly in November 2009 is very fast. Then reaching a very stable 1.0 in 2012 is also fast.
That's only possibly by reducing scope. I don't think more people help, because you can't really divide and conquer language design.
(As far as I understand, the early project was something like "take Ken Thompson's C compilers and add goroutines, garbage collection, slices, and interfaces")
I don't use Go regularly, but I've also had some very good experiences with it lately.
They have kept the tools simple and predictable. Getting started is a breeze.
The MIPS support worked surprisingly well, e.g. on a machine with 64 MB of RAM.
I've had and still have a lot of gripes about (mainly being a sort of closed ecosystem), but you can't deny that it works well, and is a big achievement.
I like Go because it does have so few features. There are plenty of other languages out there that are feature rich, not all languages have to have everything.
For me the issue of Go is that it has a focus on backend services and doesn't try going much further than that. (And also it's probably a bit too plan9'ish)
At the same time it's good that it has a focus, because that's what made it get where it got
Odd take, golang was designed by Google for their needs. It has very little uptake amongst hobbyist devs and is nearly entirely used by professional devs. In my experience hobbyist devs focus primarily on javascript/python.
Just think about golang as the next generation's Java. The same strong points (concurrency, GC, "simplicity" for junior developers), the same egregious mistakes (generics alone prove it for me), the same BigCo backing, the same undeserved early popularity.
Got into Go more than a decade ago, because it was HN’s darling back then. It has become out of fashion now here, replaced with that other great statically typed performant language (don’t want to start a flame war). Which on its turn will be out of fashion a decade from now, because we love new things.
And I love Go. It’s not perfect at all. But I get things done with it, there are tons of high quality modules to depend on, the tooling is great, concurrency / async programming is a breeze.
The nice thing is that even if Go goes out of fashion, it won’t really matter. Your old Go 1.15 programs will continue to compile and work for the foreseeable future.
This isn’t true at all and I’ve complained about it before but the go maintainers introduced a big breaking change in the x.509 standard library that caused a lot of scrambling.
I disagree that TLS is a good example of a breaking change, since this is a specific library dealing with a developing and dynamic area of security. If this weren’t changed, it would compromise PKI cert integrity by allowing obsolete fields. Also you can get the old behavior back with a flag. The fact that other languages are lagging here reflects badly on them.
As discussed in the link, CommonName has been deprecated in x.509 serverAuth certificates for decades, and all major browsers dropped support for the field (even as a fallback) years ago.
You can’t get the old behavior back with a flag as of 1.17 and if you could I wouldn’t be complaining.
Also, the reason CN is deprecated has nothing to do with security but the maximum length of the field in the spec. Chrome ignores it but every cert I’ve seen recently still includes it for legacy compatibility.
It’s not a huge dealbreaker but it forced me to go make 3rd parties regenerate their certs before I could upgrade my version of go.
No. Lots of frameworks ecosystems and languages break backwards compatibility all the time. Node stuff won't build a year from now even with the same lock file.
It definitely won't run, but I can't tell you why. Nobody can tell you why, because everything in that ecosystem is such an immensely complicated & fragile mess that there's no telling what will break; anything's fair game. Random module pulled from NPM? Surprise build step that depends on a random combination of C libraries? People reflecting implementation details of other modules that get changed with a point release? Some world conflict somewhere evoking a blackout in libReplaceCommaWithUnderscore? I don't know, but I've spent too many days of my life debugging Node module installs on the latest version, I'm certain it won't be any easier in 10 years.
Even with same lock file, modules that depend on native compilations will have problem with changing c compilers, libraries etc. Some libraries are fixed to minor version of node or node-gyp, so they won't compile with newer node. I had run into these issues both in both python and nodejs.
so, you do concede it won't work on whatever Node version is current then.
how confident are you that Node 18 will run on whatever platform you're using then? or will you be stuck on a 2023 OS image also? will all the deps be available? will the many layers of tooling still work? will they work on Node 18, or will you need to get Node 18 and Node 2033 running in the same space? etc etc.
it's already a nightmare running things a year or two apart in time in the Node world, I would guess in 2033 you'd need an entire frozen-in-amber VM image with all tooling also frozen in the same amber/.
The parent wasn't talking about backwards compatibility. They specifically mentioned forward compatibility. Something that requires continued maintenance effort, and the more versions you roll out, the bigger the effort.
I seriously doubt Go is advertised as forward compatible or that it is such in practice. Just like any similar programming language, they depend on system interfaces, and those change, albeit at a slow rate. If all older versions of Go aren't on life support, then some will become obsolete over time.
Tried to gradually upgrade projects written Ruby and Python, that were not touched in 10 years. The dependencies were based on C extensions that depended on libs not existing anymore. It was even impossible to get the original version compiled, because in was dependent on a Linux distro version that doesn't exist anymore, even as docker images.
That was the hard lesson: depending on dynamically loaded libs means you need to constantly maintain the project and even if it has no visible bugs, or doesn't depend on the code having security vulnerabilities, you need to adapt it to evolve together with the libs, and that is needed even if you actually use only what's "old" in the libs. But we're not saving every megabyte of a disk space anymore like in 90s. Switching to static compilation does wonders for maintainability.
You can vendor compiled dynamically loaded libraries, too, installing them with your program if not available.
If you actually need them to be the same version used by something else on the system, that's still a peoblem, but static compilation doesn't solve that, anyway.
It is not about vendoring. Vendored dynamic libs would be depending or other libs or even on glibc that does not exist anymore. You can't recreate the dynamic libs environment.
With static you don't need that environment, only the kernel, so yes, static compilation solves the problem.
I think the only option to make sure it will run in the 10 years is to package the linking libraries and all the dependencies. Container (e.g. Docker) or Nix can solve this, provided that these technology still live in the next 10 years.
Definitely not with Python. I'm currently maintaining a Python application and every minor version update for the language or even some of the frameworks involved comes as a high-stress process with numerous potential breakages.
Sure, but if you don’t keep moving to the new version, your application will continue to work for the foreseeable future, no? It wouldn’t surprise me to hear there are still some Python 2.7 holdouts.
Not at all. Python 3.7 and older cannot be even built on current Linux + GCC. Python has few important system dependencies s.a. libffi and libssl that since changed major versions and aren't compatible with the interfaces used by Python 3.7 (what happens in Python 2.7 world -- heaven only knows).
Python has no standard. There's no implementation of Python that say "we support versions X, Y and Z" of Python. It just keeps going and as the platform changes, it changes too. Old versions lose support relatively quickly. Three or four years if memory serves. If you go beyond that horizon, things start to fall apart, the further you go, the more likely it just be too hard to fix.
Compare this to C, where compilers typically support all C standards up to something moderately recent. Your C89 code would compile and work today to the extent guaranteed by the standard. Actual programs will have experienced bitrot of course. Same reason: system interface changes, but, unlike in Python, those aren't part of the language.
> Not at all. Python 3.7 and older cannot be even built on current Linux + GCC.
They can still be built in a suitable build environment (which is easy eg. with a container, and it is trivially easy to install a python 3.7 environment on a current linux, using precompiled binaries, without impacting the systems current python version, thanks to `pyenv`
pyenv install 3.7.17
In fact I could install all the way down to 3.0.1 if I wanted (and ofc Python2 versions).
That's like saying that Mac Lisp still works because you can put soldering iron to work and resurrect on of the two remaining Lambdas in existence.
The whole point of what I said is that it doesn't work on modern systems. If you emulate old systems where it worked, then it's a nobrainer that it will work. Like, what are you even trying to prove with this?
I've got an inherited Django project that still runs in 2.7 in a container. One day, I'll get the opportunity to rewrite it into something more suited to the task, and that'll be the end of 2.7 for me, but for now, it still does its thing.
Go has entered the "boring technology" phase, along with Java, .NET, etc. It's a safe, productive choice, with well understood upsides and downsides. There's not much to talk about... and that's ok!
It's also very fashionable compared to other garbage-collected languages, which is arguably a more sensible comparison. Though one could argue that ReasonML (an Ocaml frontend with vaguely Go-ish syntax) has the better overall design.
It's not only about echo-chambers. Rust is an emerging language, it's enthusiasts feel compelled to promote it to help it grow faster. Go is already well established and it's users likely feel that this language will be fine without their advocacy and more likely to stay silent. Go generated much more excitement on HN when it was not yet widely used but was growing and promising.
If you look at github stats go is still growing I don't see how you think it is its not used anymore it is the only largly used language that is still growing.
Touche. Got into Wails/Go recently for a desktop app. Everything fell in place in a short time. Even the junior contractor offshore with zero experience is able to contribute! (thanks to Wails also here .. using Svelte for the frontend .. love the lightning fast builds)
There are a few surprises of Go, but one thing that surprised me the most is the fact that an unrecovered panic in any goroutine will crash the whole program. To make things worse, you can only recover a panic in a goroutine that you spawn. So, you can't do anything to prevent a panic in a goroutine spawned by a third-party library crashing your whole program. The best you can do is to set up a supervisor to restart your program after crash.
Rust made the same call. Panic isn't supposed to be "I'm not sure what to do now, maybe my caller will know?". That is an error not a panic amd you return it. A panic is for "I just realized I or someone else did something really bad and the invariants of everything are now in question, hit the breakers, lets try this again". If your dependency library, like so many, panicked rather than take the time and effort to check the conditions that would cause an out of bounds access, then they are bad and they should feel bad, fork and merge the fix and don't say anything, they will feel the shame.
Completely different. In Rust when you spawn a thread [1] or an async task [2], you'll get a handle from which you can check if a panic happens inside the thread/task. Even if you ignore the handle, a panic inside a thread/task won't crash the entire program.
Yeah I've been hit by this, I've setup a panic handler to purposefully kill the entire process because otherwise long-running threads die and don't restart (and it was easier to do it that way).
- in theory an async runtime could shut down on panic, but tokio at least does not, much like `std::thread::spawn` it'll catch_panic and return a JoinError to however is await-ing the joinhandle: https://docs.rs/tokio/latest/tokio/task/struct.JoinError.htm...
If you drop the handles, the error just is not signaled:
Yeah I think I remember that being a controversial decision when it was made for threads? One of the valid reasons for a panic is that you've encountered something that makes you think your adress space is in a bad state. Threads share an address space, so the reasoning was that if they panic, so should the process, but that isn't what happened.
So the question is: should there exist a method that means “bring down the whole ship”. (Or, should that method be something more severe than “panic”, and panic should be redefined with a more limited scope.)
I agree. I like C# where you can expect libraries to play nice. In theory it is possible for C# library to crash your app but it is definitely not on the trodden path.
But I do prefer return values over exceptions! I have not used Go and I think the err != nil boilerplate might drive me a little insane, but Haskells option types seem a nice fit.
> Panic isn't supposed to be "I'm not sure what to do now, maybe my caller will know?".
So panic is supposed to be “I’m not sure what to do now, I don’t care if my caller knows”?
No process running outside of kernel space ever has any business panicking. Throw an error and move on with your life (or don’t, that’s fine too) but don’t punish your caller by forcing it to crash because you failed to foresee X, Y, or Z circumstances.
Panics are the result of conditions that absolutely should crash the program! They are not errors, they are not similar to exceptions, they are conditions that should not occur, and thus if they do occur, either the program itself, or the platform it is running on, has a problem that needs to be solved before the program runs again.
If a cars fuel is running low, it should turn on a warning light and inform the driver that he should find a gas station. If a piece of the road is suddenly missing, the car needs to stop.
If a panic occurs, I don't WANT the program to continue executing! That would lead me right back into C land, where an out-of-bounds memory access would silently start to poison my program and crash it days after the actual error ocurred, if i'm lucky! If I'm unlucky, and the process needs to be sufficiently privileged, it would kill the server, again several days (or weeks) later, with slim-to-no chance for me to figure out what even went wrong.
For a real-world example of panic and recover, see the json package from the Go standard library. It encodes an interface with a set of recursive functions. If an error occurs when traversing the value, panic is called to unwind the stack to the top-level function call, which recovers from the panic and returns an appropriate error value
That's panic and recover used for control flow, just like might use exceptions in other languages.
Looks like panic has at least two purposes, depending on whether you recover or not?
However, this is a poor example and a misuse of panic and recover as exceptions.
See https://golang.org/doc/effective_go.html#panic
The example will be invalid soon with the release of go 1.11 as well. The
panic/recover was removed from the unmarshal code. See master's
So no, panic and recover are not meant to be used as control flow. Yes, they CAN be used that way. I also CAN use pythons `eval` a lot, use a scripting language as my command line interpreter, or make an apple-pie with onions and garlic.
As for why `recover` exists in the first place: Packages should have the ability to stop internal panics. There are various reasons for that, some worse than other. For example it is probably okay if a package doing sensor readouts recovers from a division by zero and replaces it with an error saying that the sensor value makes no sense.
And why exactly do you trust a called method to determine whether or not you should be able to keep running? Like… do your job, you object. I didn’t load your library and call a method on you to tell me what the state of my system is, I just want you to either do what I asked you to do or fail gracefully, and then I can decide whether we crash or not.
> If a piece of the road is suddenly missing, the car needs to stop.
Uh, no. If a piece of the road is missing, it’s up to the driver to stop, not the car. The car has no business whatsoever determining whether the road exists or not, except inasmuch as it communicates to the driver “hey, whatever the heck this is is difficult or impossible to drive on, your call what we do though.”
> just want you to either do what I asked you to do or fail gracefully
And most of the time, this is EXACTLY what happens. In fact most libraries that do use panics, recover them internally and return errors to the callers.
Again: panics are not errors, panics are not exceptions. A library should never panic in a way that it's caller notices, UNLESS it met a condition that simply is not recoverable, not by the library itself, and not by it's caller.
Yes, this requires care by the libraries authors not to overuse panics. Yes, there are libraries that do overuse them. No, this doesn't change anything about what I wrote. If a library I use would do that, I would either fix the library, or find a better one.
> Uh, no. If a piece of the road is missing
Okay, let me try again with a better analogy:
If a cars engine breaks down, the car will stop. It doesn't matter if the driver wants the car to go for another 100 kilometers. It stops. Period. Not because anyone or anything "wants" the car to stop, but because its engine "package" met a condition, under which the further running of whatever context it is used in (in this case, the car), is no longer feasible and/or possible.
That is exactly what panicking in a way that hits the runtime communicates: Oh noes, the engine just broke down, and now the machine can no longer run.
I’m not sure how valuable these analogies actually are, but your example of the broken engine really seems to go towards my point. If the car breaks down, the driver still has several options: coast downhill, switch to a different car, pull a bicycle out of the trunk, or just get out and continue on foot. But the car enforcing the end of the trip by simply exploding and killing the driver isn’t really an acceptable response to engine failure.
A library that unrecoverably crashes your program for any reason is defective by design, and a programming language that purposely allows a library to do so as a design choice is also defective by design in my opinion.
Again, the library isn't "crashing the program". The library meets a condition under which it can no longer function, no matter what the caller does, and can no longer function in a way that makes running the entire program infeasible. That is the point of panicking and not recovering it within the library.
And yes that is the libraries call to make. Yes, it needs to have a very good reason to make that call. Very few libraries panic in that way. And the ones that do, and are widely used, have very good reasons to. If libraries do so without a good reason, then people stop using them.
> The one option he doesn't have though: Continue running the car as-is. And that's the point I am making.
Exactly! So raise an EngineFailure exception and let the driver decide how to proceed. But don’t kill the driver because you can’t figure out how anyone could possibly recover from what seems to you like the end of the world, when it might just be a speed bump as far as the driver is concerned.
I mean, a lot of people are fine with programming in PHP, that doesn’t make it a good choice.
> And yes that is the libraries call to make.
No. Absolutely, 100%, beyond question not. The library can’t possibly know every conceivable circumstance in which it is called, and therefore it is a guarantee that there is some conceivable set of circumstances from which what appears to be a fatal error from the library’s perspective is nothing more than a blip, or even a complete non-issue, from the perspective of the caller. Panicking the calling program so it can’t possibly continue is an abuse of trust, and any library that does so shouldn’t be admitted to any repo, and any language that allows it to do so is broken by design and should be considered harmful.
> Exactly! So raise an EngineFailure exception and let the driver decide how to proceed.
In the analogy, the car is the program. The ENTIRE program. It is the limit of the analogy. The car driving is the program running. The car no longer being able to drive, is the program not running. Everything outside of that is out of the scope of the analogy.
> I mean, a lot of people are fine with programming in PHP, that doesn’t make it a good choice.
That is absolutely true, but it doesn't make PHP "defective". If someone is unhappy with how Go's `panic` works, they can use something else, same as I am not going to use PHP.
> No. Absolutely, 100%, beyond question not.
Yes, absolutely, 100%, beyond question it is.
The library is an implementation hidden behind a public interface. Same as I don't have to worry about how it performs it's functions, I don't have to worry how it determines what it considers to be an irrecoverable failure condition.
If I disagree with a libraries decision on that, I have the same options I have if I disagree with how it's interface works (assuming it is open source): 1) I can fork it and roll my own implementation. 2) I can vendor-copy it and adapt it locally for the scope of that project. 3) I can use another lib (including writing my own from scratch).
> The library can’t possibly know every conceivable circumstance in which it is called
It doesn't have to. If the engine breaks down, it does't matter if the car is yellow, the sun is up, or the driver was born on a Tuesday. The engine is broken, and cannot run. End of story.
If there are conceivable circumstances under which the lib should continue running instead of panicking, it is the libs job to implement it, same as it is its decison what these circumstances are, and when they apply.
> and any language that allows it to do so is broken by design and should be considered harmful.
Well, I think we are gonna have to agree to disagree on this. Considering Go's success and continuous growth, it is pretty safe to say that many people and industries don't consider it broken, nor harmful.
> I just want you to either do what I asked you to do or fail gracefully, and then I can decide whether we crash or not.
Then you're looking for resumable conditions, which just so happen to be effectively isomorphic to async-await. (The called function is effectively a piece of async code which 'awaits' upon a failure condition, after which it can either be resumed or canceled as with an ordinary panic.)
And how is the called method supposed to be able to determine whether the state of the world is irreconcilable?
For that matter, how is the called method supposed to determine what the state of the world even is? And why on Earth would we trust it to do so? Is encapsulation not even a thing anymore? I really just don’t understand.
There are two "kinds" of panics: those initiated by the Go runtime, and those initiated by the program author, though technically there isn't a distinction. When you access an array out of bounds, the Go runtime initiates the panic. On the other hand, for a condition that the program should hold but doesn't, the program author should initiate the panic. For example, in the exhaustive switch below, reaching the default case means that either you have somehow incorrectly allowed the construction of an invalid token value earlier in your program, or you have missed to account for a valid token value in this function.
func (t token) symbol() byte {
switch t {
case add: return '+'
case sub: return '-'
case mul: return '*'
case quo: return '/'
default: panic("unknown token")
}
}
Errors are different. For example, url.Parse returns an error if its input string is an invalidly formatted URL.
That is a great example of a reason to panic. There is nothing you can do at that point but return an error or panic, and something has really gone wrong if you are pulling bad tokens out of a hashmap you made yourself, tonthe point I think you should panic. Sadly, almost all the panics I see coming out of rust crates are because someone called unwrap instead of handling the error case.
That is a terrible example of a reason to panic. If the programmer believes the list to be exhaustive and it isn’t, then the programmer is wrong. So raise an exception and let the caller decide what to do with it. But don’t punish the caller by forcing it to crash just because you made a mistake as a programmer.
(in go, "raise an exception" means "return an error.")
error values are meant to represent expected, unsuccessful conditions—internal properties of the program—during the program's execution. for example, looking up the address for a domain name is an operation that can be expected to fail at times, and an error is appropriate here.
errors in go are not meant to represent properties external to the program, such as mistakes by the package author or documented incorrect usage of the package by the package user. in go these are panics.
perhaps other languages use exceptions for both but that conflates.
Sounds like you may be confused thinking that a panic in Go is like throw in other languages? Third-party libraries should virtually never panic. Go has panic, and it's useful for a few things, but does not use them as exceptions are used in other languages.
This is a bit like worrying that your programming language won't prevent a third-party library from sending a SIGKILL to your program or that it doesn't prevent third-party libraries from delete your entire file system.
No general purpose language protects against malicious or stupid code.
I've seen apps break in prod where third party libraries had array accesses on indices that didn't exist, or dereferenced nil pointers, in goroutines they spawned. And those aren't the only ways to cause a panic without ever calling the panic function.
Third party libraries have bugs sometimes, and other times our usage of them can create bugs. Nothing like a SIGKILL or anything malicious. That's just the way things are and will be. And breaking down like this in dev can be fantastic!
But when a once in a blue moon issue crops up in prod and all other connections are dropped, the fact that a simple defer+recover couldn't have been preemptively placed on all goroutines is a huge nightmare. I'd even take a "you can add defer+recover for any goroutines created further in this callstack" - it's rare that libraries spawn goroutines, I'll already know which ones to trigger that behavior on
> But when a once in a blue moon issue crops up in prod and all other connections are dropped, the fact that a simple defer+recover couldn't have been preemptively placed on their goroutines is a huge nightmare
Yeah, this is one of the weird things, to say it mildly, that Go does that is hardly mentioned anywhere. Most people don't know about it until they themselves first encounter the nightmare bug similar to what you said.
Wrapping every third-party library in defensive code doesn't make a lot of sense since this isn't the most dangerous kind of bug. To make a one example of a million: a third-party database library that corrupts your database is infinitely more dangerous and that there's no form of wrapping that will save you.
Bad code is bad code. Bugs are bugs. This issue is not a major source of problems in Go code.
> Sounds like you may be confused thinking that a panic in Go is like throw in other languages?
Not at all. I knew that panics in Go shouldn't be used for error handling in general.
> Third-party libraries should virtually never panic.
True, as true as 3rd-party libraries shouldn't have any security vulnerabilities. But we don't live in a perfect world. Bugs happen.
> This is a bit like worrying that your programming language won't prevent a third-party library from sending a SIGKILL to your program or that it doesn't prevent third-party libraries from delete your entire file system.
Very different. Your examples are intentional and malicious actions. What usually happens is due to bugs.
> No general purpose language protects against malicious or stupid code.
Are you implying that all bugs are caused by either malicious code or stupid code? Panic-related bugs are often due to the billion dollar mistake [1] that Go's team refused to solve.
> but does not use them as exceptions are used in other languages
IIRC, the Go lexer/parser itself relies on panic/recover to handle errors[0], in an exception-like fashion. Granted, it's one unusual case, where the code is split in a multitude of mutually recursive functions.
Yes, "usually", but not always. That's why Go has a mechanism to handle a panic with the `recover` function. The issue is more on the inability of handling a panic in a "foreign" goroutine.
A panic means your program has entered an invalid state. Continuing to run an arbitrarily broken program would be reckless, so crashing is the only option.
Even if you could somehow prevent your program from crashing internally, it could be killed at any time by the environment it's running in, up to the cleaning lady tripping on the power cable.
That's why it doesn't make sense to try to prevent crashes. Instead, programs should be capable of resumption irrespective of when they were killed. Resumption should be facilitated by supervisors layered on top of the program, as needed.
Panics are definitely one of the bigger design mistake of go. They should have either removed panics entirely and made error as return values the only way to do signal errors or they should have made defer+recover more mainstream like exceptions.
Current state of affairs is that third party libraries panic where they shouldn’t, programs don’t defer where they should, recovers cause deadlock due to non deferred mutex releases, that assumed panics are panics and not exceptions. On top of that we have to live with the verbose error handling that still isn’t water tight.
Good tooling out of the box. Very predictable dev. trajectory. Became my go to language for most personal projects. My only remaining ask would be sum types.
Sum types are amazing! Once I got introduced to them in Rust, it was hard to go back to languages like Java where the enum type is pitiful in comparison.
Not familiar with Java, but C# has sealed classes as well. They are not the real deal. You can fake your way around to achieve something resembling sum types, but the deep language integration like in Rust is simply unattainable. For example, ADT and pattern matching go hand in hand, so you want syntax-level, first class and powerful support for structural pattern matching as well. Not sure about Java, but last I used it C# had only relatively basic support.
Java actually does have pattern matching like this:
int eval(Expr e) {
return switch (e) {
case ConstantExpr(var i) -> i;
case PlusExpr(var a, var b) -> eval(a) + eval(b);
case TimesExpr(var a, var b) -> eval(a) \* eval(b);
case NegExpr(var e) -> -eval(e);
// no default needed, Expr is sealed
}
}
Wishing that eco system the best then! ADTs and pattern matching are the best thing since sliced bread.
Sometimes I wonder: will they become table stakes in the future, or will we wake up to their (currently unknown) downsides in a decade or two? Think of how exceptions are now considered a poor pattern, compared to errors as values.
It's funny how languages like Go and Julia are typically still seen as "new", even though they are more than a decade old. It's similar to "modern C++" referring to C++11 and above. Sometimes you even read about "modern languages like python" which is more than three decades old.
Depends on what people mean by modern. Modern doesn't have to mean recent. Python for all its problems is keeping up to date with lots of modern ideas: optional typing, decent packages ecosystem, async support, refreshed format strings, etc. It's a different kind of modern than C++11 aka "default compilers in LTS releases finally support it, so we can set it as the used standard" kind of modern.
Minimalist would love go. It is hard to over engineer when there's no inheritance, no meta classes, no properties, iterators, decorators and such.
I've seen pretty convoluted code bases otherwise that showcase the engineering muscle of the individual and how they can employ almost every single language feature even to a very narrow business domain.
That is an assertion which makes no sense to me. Go is not a minimalist language, it's a restrictive one. Although it made different choices, it's in the same vein as Java, and certainly no Forth, or Smalltalk.
> It is hard to over engineer when there's no inheritance, no meta classes, no properties, iterators, decorators and such.
None of that has anything to do with over engineering?
What restrictive and how? Whole cluster orchestrators have been built with go (k8s, nomad), whole databases have been built with go (cockroachhDB), whole authentication systems have been built with go (zitadel, authelia), whole web servers have been built with go (Caddy, Traefik), whole DNS servers have been built with go (CoreDNS), whole key value stores have been built with go (etcd), some encryption has been built with go (age) so how it is restrictive and why didn't it restrict all those use cases?
Not to mention, if you every spawned a container, there are 99.9% chances that somewhere go was involved along the way. There are other run times around (rust comes to mind) but none is production ready.
Point is - restrictive should have restricted. It hasn't
Minimalist isn't restrictive.
EDIT: Added docker and restrictive not the same as minimal.
The language is restrictive, in that you can't "play" with the language, it has a very fixed syntax / structure and no real way to make your own, much like Java or C#: no macros, limited runtime reflection (to say nothing of wholesale manipulation), control structures are heavily syntactic, etc...
Hell, the designers built in a bunch of collections and behaviour (and special syntax) around those collections because they either did not want to or could not be arsed to make the language able to express them.
> Whole cluster orchestrators have been built with go (k8s, nomad), whole databases have been built with go (cockroachhDB), whole authentication systems have been built with go (zitadel, authelia), whole web servers have been built with go (Caddy, Traefik), whole DNS servers have been built with go (CoreDNS), whole key value stores have been built with go (etcd), some encryption has been built with go (age) so how it is restrictive and why didn't it restrict all those use cases?
That has nothing to do with anything? Literally all you're stating is "the language is turing complete therefore it can't be restrictive".
>None of that has anything to do with over engineering?
Most over-engineering in the wild overuses inheritance. That's why we see more monstrous "enterprise" code in Java and C# than languages like Go and Rust that lack inheritance.
> That's why we see more monstrous "enterprise" code in Java and C# than languages like Go and Rust that lack inheritance.
Rust has a completely different niche, and maybe wait a bit before claiming anything like that about Go - it is not being used for that kind of enterprise systems just yet.
I'm not sure here how you differentiate minimalist from restrictive. I've always considered Go to be minimalist in terms of available tokens to the programmer:
No language on this chart has even a passing resemblance to minimalistic. I don't think anything does when it reaches double digit keywords.
For reference, I believe Smalltalk has 6.
And forth is more complicated because it doesn't really have keywords at all, and barely any syntax, instead it has assembly-coded / runtime-provided words (~functions) and variables. SectorForth (https://github.com/cesarblum/sectorforth/) is down to 8 builtin words, 2 IO words, and 5 variables (milliforth packs those behind a word instead). And so far 2 of the words have been found unnecessary / redundant.
Really? It's the Go is great if you're staying within the simple confines of the designer's vision, but if you ever need anything beyond that, you have to write some unidiomatic go code that no one will be able to parse. Just look at any JSON parse library, look at struct "annotations" as this massive kludge for injecting struct metadata, etc.
I've been programming in Go professionally for 6 years, and it covers 80% of usecases well, but that last 20% is really tricky
Iterators are coming. But I think taking very long on adding features such as generics and iterators has a positive effect of repelling the ones who over engineer the most.
And because this is HN, I have to mention of course you can over engineer current Go as well. But it's not "fun".
It should have been called "issue 9", which would have been a great name, expandable to "Issue 9 from Google Labs", very unique, nicely abbreviated to i9 for compiler and filename extension.
I was there, along with many others, and I'll never forget that this language has a dumb name and it could have had a much better name:
Golang's team approach of being "boring" sometimes tricks people into thinking that nothing happens with the language. Good article to show exactly how much work is done.
As a fan of Go the only really thing I’m missing is a better way to work with data. Something like LINQ probably wont ever be possible with Go but built in support for map/filter/reduce with some handy built in aggregations would make a lot of things simpler to accomplish.
I’m not asking for the full Java streams equivalent, it would just be nice to be able to filter a slice or perform basic transformations without having to resort to for loops.
This is a lot more possible now that Go has generics (as of 1.18).
I would probably never use these, as I find such libraries are a whole new domain-specific language to learn, and often don't make things much simpler anyway, but here are some libraries where people have done something like this:
Fourteen years of Google language published on Google platform that first and foremost addresses internal engineering and culture issues of Google. Also, anemic and slow standard library.
Given Golang popularity, it probably managed to solve more than Google's internal issues.
It most likely ticked quite few boxes right:
* easy syntax
* fairly extensive std lib
* easy memory management & mostly painless GC
* easy shipping of dependencies (everything in one file, including runtime)
* order of magnitude less tricky parallelism.
In a world where Golang doesn't exist, quite a few recent software would probably look really different:
* Etcd but in Java (hello Zookeeper)
* Terraform but in Python
* Docker but in C/C++ or Python
* Kubernetes, but in Java and/or Python and/or C++ (hello Mesos)
* Prometheus, but in Java and/or C/C++
And honestly, for most of these, either because alternatives in such languages do exist and mostly failed, or from performance/stability concerns, I think we would not have been better of.
Golang is not a complete replacement for Python and/or Java and/or C++.
For example, if you were to create a complex native application (browser, game, cad software, etc), C++ is still probably the best choice. For data manipulation/transformation or glue scripting, Python is still a more convenient choice. If you want to structure an extremely large codebase and depend on tons of "Enterprisy" stuff, Java is most likely a good choice. (note: I'm caricaturally oversimplifying)
Also software is rarely created in a vacuum:
1) software teams have certain skills and habits
2) new software is often created in a preexisting context
3) In fairness, language choice is often not that critical
4) Developers can be extremely vocal about technology choices, often way beyond the reasonable
For all these reasons, even if we fall right in the Golang niche (small to medium size web api/app, "devops" tools/infra management and partially, some DB use cases), choosing Java/C++ over Golang could actually be the right choice.
Most people like indent width 4. Some like a compact 2 or 3. Sometimes even 1! Others need 8 because of their eye sight. What should the indent width be? It should be TAB because anything else is wrong.
I have always felt there has been a learning curve for Go and Rust when it comes to syntax for me. I have used C C++ Java and Python and PHP for a long time but whenever I start Go or Rust, over time I lose interest thinking this is too complicated and difficult for me (no idea why).
Is there anyone in the same boat and are there ideas how I can make myself get good with at least one of these trending languages ?
I found rust very simple to pick up because there's a lot in the language that really helps you to write the code the way rust needs it to be. The language design is really good that way, and the compiler helps a lot. What's tricky is to understand the underlying concepts like it's module structure, ownership, etc.
Writing practical code is harder if you're not used to it because it enforces discipline that a lot of coders in other languages don't care about like the mutability of a variable. For me it just clicked because that's something I always struggled with in other languages and it frustrated me that I wouldn't know how a variable was supposed to be used. The fact that in rust that concept is built into the language makes me excited. If someone doesn't care about that kind of thing then I can see it being very frustrating.
With Golang, I would strongly recommend ignoring all advice. People kept saying it's easy to understand, that it's like python, or that it's like a better C. Forget all that, try to approach it from the ground up as it's own separate thing, dive into what interfaces, slices, etc. actually are. Then write a bunch of practical go code like a webserver, then a lot of go idioms become a lot more obvious. I really feel like there were some lanuage design decisions made where one thing exists because of another feature, so writing some code makes those links obvious as opposed to learning each feature independently.
For me Go is one of the easiest languages to learn (even if there are some low level bits like closing bodies etc.) just because you always know what goes in and comes out of a function.
When doing Ruby after only using Go for years it was (and still is) very tricky for me to deal with error handling and figuring out what kind of object I'm dealing with.
First of all don't try to learn both of them at the same time you just add unnecessary anxiety. And as others have said, Go is much easier to learn that Rust, both the syntax and the underlying concepts are simpler. Just go through the Go playground tutorial on the site or try rewriting some of your C/C++/Java code. It is not as good a replacement for most Python and PHP code in my opinion.
I find a good IDE that you are already familiar with from other languages/environments goes a long way. Pick your poison, but for me because I'm already using IntelliJ for Java at work and Android Studio for open source projects, I was able to get into Go relatively easily recently using IntelliJ.
The linter/autocompletion/auto-fix/refactoring/etc made it much simpler to a avoid having to rote-learn the syntax for functions, lambdas, structs, etc. To go with that, the error handling becomes much easier to learn because the editor is able to tell you when you've got the wrong number of return values / wrong type of values.
Yes, the compiler does all of this, but the way it happens in real time using the same keybindings/UI/UX that I use for my day job makes it all that much easier.
Good news! If you’re familiar with Python, Go is at its heart not that different. It has fewer features and the syntax is similar, which makes it easy to learn.
Try the interactive Go Tour, and review the Learn X in Y Minutes page for Go to get familiar with the new features. Then try building a fun toy project in it. Go makes it easy to build web servers, so maybe a REST service is a place you could start.
There are a lot of issues one can point out about Go, it just so happens that what they picked sure makes the language compelling to me.
Huge, working (unicode, streaming, etc..) stdlib that doesn't require 20 different third party packages to get a server running. Also has built in formatting, testing, and benchmarking(!)
Compiling is so fast I can write tests that run on save in ms showing me outcome before I start typing again and actually enjoy TDD. This isn't possible with TypeScript, C++, or Java.
I'd be happy for every other language except Rust to disappear tomorrow, but aside from that Go is just nice and unfussy to get really good performance with a quick dev story.
> Compiling is so fast I can write tests that run on save in ms showing me outcome before I start typing again and actually enjoy TDD. This isn't possible with TypeScript, C++, or Java
Go is one of those languages that will be around after 30-40 years. Most likely lots of code today will still compile in 30 years. Go has succeeded and is the defacto networking language today.
I'll never forget when I was a freshman in college in 2010, someone from the language team at Google came to give a talk on this new little Go programming language they were working on. (For the life of my I can't remember who it was and that makes me very sad.) And ironically at the time my biggest thought was seeing the hand-drawn gopher and thinking "THAT'S the logo they're going with?!"
At that time it was this cool but incredibly niche new thing...and now that gopher is iconic and the language is extremely popular. Who would have guessed what it would become?
I think becoming the go-to language in the cloud native/kubernetes landscape has really helped the popularity as well.
Personally between Go, Ruby, and some Bash I never feel tempted to reach for any other languages to get my work done.
We're actually even at the point where Java has added language support for sum types and pattern matching!
Java is also the most prominent example of a lack of null safety. Once Java gets around to fixing that in the next century or two, then Go will really be behind.
Go is a great language. The Python of typed languages, if you will.
Is it perfect? There's no such thing. But it's very easy to get to work and start solving problems with no headache, which is probably why so many very popular tools are written in it these days.
It's worth reiterating that Go was created by Google to solve Google's problems. If it also solves your problems, or a worthwhile subset thereof, then that is a happy serendipity, but it's not a design goal.
"The key point here is our programmers are Googlers, they’re not researchers. They’re typically, fairly young, fresh out of school, probably learned Java, maybe learned C or C++, probably learned Python. They’re not capable of understanding a brilliant language but we want to use them to build good software. So, the language that we give them has to be easy for them to understand and easy to adopt." – Rob Pike 1
The problem Go solves is that google has a bunch of people who can hack out code, but don't really know the theory of computer science, can't understand high level abstractions and complex type systems... So they need a language for "the average programmer", or as pg would say, "the blub programmer".
They invented the blub language. Blub programmers are everywhere at google, and they are everywhere in the industry, so its primary design goal is applicable to almost every company in the world.
I agree that this is a real problem, but newer languages like Swift and Rust are targeting newbie coders while keeping the benefits of advanced type systems. The key is to surface errors in compiler diagnostics, so that the newbie coders know when they're getting it wrong. It's using the compiler as a friendly TA. Golang doesn't really solve the problems of newbies, it just punts the issues to runtime where they will likely end up causing breakage in production.
> Golang doesn't really solve the problems of newbies, it just punts the issues to runtime where they will likely end up causing breakage in production.
The GC in Go punts the issue of memory management to the runtime, but does so in a largely correct way. In that one way, Go is easier than rust in a way which genuinely does not cause any real production issues.
However, the most important tradeoff when talking about Go vs Rust on this startup-infested hellsite is not related to the quality of code at all.
The goal of a startup is not to produce working production code, but rather to convince VCs of its trajectory. VCs don't care about buggy code. Every company has buggy code, it's expected you have bugs. Normal. Possibly even a good sign. "Move fast and break things". What matters more is the number of warm programmer butts you have in seats. VCs want you to hit your headcount targets, and unfortunately, hiring programmers that will make your codebase a buggy spaghetti mess is much easier than hiring Rust programmers.
Said another way, good code might actually be a problem in startup-land since it will make it harder to meet VCs hiring expectations.
> The key is to surface errors in compiler diagnostics, so that the newbie coders know when they're getting it wrong.
That "key" is making Rust the complexity issue it is. Which is why it is considered so hard to pick up, both by newbies, and by experienced programmers. And yes, I know, there will be people who say "Rust is easy!". Well, show me people who learned most there is know of Rust over a weekend, or who understood a large Rust codebase while still learning the language. In Go, I personally know several people who did both. In Go I onboarded people who never before even saw any Go code, without an issue.
> Golang doesn't really solve the problems of newbies, it just punts the issues to runtime
These are not problems of newbies.
Yes, GCs are an adequate solution to the complex problem of memory management. Most real world production code written doesn't have performance constraints that prohibit the use of a GC.
No, it was created by a group of distinguished engineers who just wanted to create a language they liked. As I understand it they wanted to improve on C but take a radically different direction from C++, which they disliked.
Rather than being explicitly designed for problems Google was facing, it took a fair amount of time to find a niche (initially, log processing).
Go was created to solve the problem of how to write real world code solving practical problems in the space of backends, systems (not system) programming and microservices, where performance matters, with a language that is easy to learn, the code of which is easy to read and maintain, which is easy to understand for tooling, and which lends itself naturally to projects where many coders of different skill levels and personal preference work on the same thing.
I believe that it is very much a problem many entities that aren't Google like to have a good solution for.
These things are quite subjective. In practice though, Go lacks the kinds of features that would make very large scale codebases (the sorts of codebases where "many coders ... work on the same thing") truly surveyable, understandable and easily maintained. I mean, it's not a complete disaster like Python or JavaScript. It has some features to support modularity and programming "in the large". But other modern languages like Rust or even Swift are quite a bit better on that side of things, and it shows.
There's an aside to that: Supposedly within google itself, they have super-magical tooling (ie: rename `dev.foo.bar(abc)` to `abc.dev.v2.foo(bar)`), so it's less important to have those "in the language", when "they" can just refactor "everything".
Us mere mortals are trying to pick up Thor's hammer at the end of the day.
And yet the largest Go projects seem to be doing fine, dwarfing the largest Rust or Swift projects in the level of activity, number of contributers, and general impact on the ecosystem. And I speak as someone who prefers to write Rust.
Perhaps these features you speak of don't matter as much as we think.
I've come to accept that Golang, as bad as it is, is probably the best that we're going to get in the near future.
It takes so long for a language to rise to prominence that newer languages like nim or zig are probably a decade away from even gaining a threshold.
I really wish there was a better alternative to golang because it comes so close to greatness: fast compile times, small binaries, GC, great concurrency ergnomics, great for networking (My God! you can just use net/http for so much - it's insane), an attempt at simple code (yea checking err is boilerplate, but it makes error handling clear), a community focused on having few dependencies where it's possible to build something and have it work for years.
But then there's complete bullshit like the nil handling, a lot of the interface design, the horrible enum equivalent, using casing to indicate accessibility, how tacked on generics are, how long it took to get decent package management when there was so much existing knowledge to learn from, etc. There's also some features like sum types that would be really nice to have.
I think your criticism of Go as it is right now is basically:
1) nil handling is bad
2) Missing enum/variant/sum type
3) Non-specific dislike of interfaces and generics (care to expand?)
4) Some style decisions, like case-sensitivity
Honestly, that's not a bad score for a language like this. If I were to list off things I dislike about virtually any other programming language, it'd be a lot longer.
It's even more ok, because some type of enum type can still be added without breaking the language.
I do agree about nils, though, that design is just bad and we're probably stuck with it.
Yeah it got so much right. I understand why generics were not part of 1.0 release. But I just can’t figure out how nil is better than option/result especially if you already have a generic map and an array as builtins…
I think nil is in the language because it keeps the compiler simpler than if it had to support an extra construct that exhausts the variants of option and result. In principle, you could implement a check, e.g. in govet, that tries to prove a value can't be nil and, if it can't, requires a runtime check. This is how things work, e.g., in eBPF, and it's fine.
What really irks me about nil is that it's typed, and the (runtime!) type is part of the comparison. So checking "err == nil" can return false in case of "nil == nil" if you ever return a concrete error type rather than the interface. Now that is some crazy nonsense, and probably unfixable at this point.
Well, actually I think once you decide that any nilable type has to be so annotated (e.g. as an option type), that changes the language quite a bit. I’m not so sure there is any evidence that programs written in such languages are simpler. In my experience, e.g. Rust and C# programs tend to be more complex than Go programs. Of course you might say the opposite, my point is that there’s no real evidence in either direction.
And dealing with such type annotation is much more than a tiny bit complex. Constructs usually used for this tend to be some of the most complicated things in any language that has them. (Rust’s match alone might even have more grammar to it than all of Go).
Finally correctness gets thrown around a lot, but what does it mean? Disallowing nils isn’t a magic bullet, in fact in most practical programs I’ve seen, values where the option wrapper is empty panic anyway. And even if you could eliminate all null pointer errors, how big a win is that really for correctness? I haven’t seen a null dereference error in Go code in a while.
I have good hopes for Kotlin native to start being an alternative to things like Rust and Go. The compiler is already there. The ecosystem is just a bit lacking with libraries and Kotlin just is not used a lot for things like command line tools and other things where Go tends to be popular. Mostly the focus with Kotlin native is on IOS right now. But you can build windows/linux/mac binaries just as easily. Things like ktor actually support kotlin native as well. And of course there are a growing number of multiplatform libraries and things like the wasm compiler for kotlin getting closer to being ready.
> I really wish there was a better alternative to golang because it comes so close to greatness: fast compile times, small binaries, GC, great concurrency ergnomics, great for networking (My God! you can just use net/http for so much - it's insane), an attempt at simple code (yea checking err is boilerplate, but it makes error handling clear), a community focused on having few dependencies where it's possible to build something and have it work for years
...to congratulate Go as another language where concurrency ergonomics is baked into the language.
While Go has another philosophy in mind, it works for many people. Together they opened the door to make concurrency a more important language feature and undoubtedly influenced other popular languages like java.
I'm a reasonably new go programmer, I've only been doing it full-time for a few months. But "great concurrency ergonomics" has.. not been my experience.
What I've been finding is that primitives it gives you are easy to use, but extremely hard to use _correctly_. My first major PR in go had a week of back and forths as more experienced go programmers on my team pointed out the many, many places where my concurrent code was buggy -- places where I was receiving without selecting over the context being cancelled or some close-channel, places where I was sending from a goroutine that had to have a nonblocking basic loop without guarding with a default clause, places where I was using an unbuffered channel where pathological goroutine scheduling could result in a deadlock, places where I was using non-thread-safe data structures in places that could theoretically be mutated by multiple goroutines and without spamming mutexes everywhere, etc. etc.
And sure, I'm relatively new to go. I'll learn these things and get better. But the contrast to some previous work I'd done in Elixir was striking. In Erlang/elixir, _the obvious first thing you try is generally actually correct_. And it gives you standard ways of working built on top of the primitives (genserver calls etc.), so you don't have to buggily reinvent the wheel every time you want to send a message from one goroutine to another with a reply, or need to tear down a bunch of goroutines simultaneously.
I have a highly-concurrent elixir service I wrote several years ago, by myself (early stage company, no code review), still in production. It pretty much never has problems and basically just worked from the start. If I'd tried that with go, without a bunch of experienced go programmers to point out all the subtle race conditions, I'd probably still be dealing with a long tail of data races and deadlocks years later.
I wouldn't describe Golang as "concurrency ergonomics" You can inadvertently share mutable data across Goroutines (Golang's term for userspace threads or fibers) in a memory unsafe way.
If you are creating a backend service, the language of choice is clearly Go. Get productive within a week, straightforward to read, compiles to a single binary (easy deployment), suited for serverless functions, low memory usage , excellent ecosystem, static typing, composition over inheritance, sane dependency management, Goroutines instead of async/await, super fast compile times and very very performant.
[Vs Language]
Python: Very slow, dynamic typing, async await, multithreading is an issue, deployment not as straightforward, messy dependency management
Java/C#: On the heavy side for resources, verbose and various design patterns, async await, around the same performance as Go
Nodejs: Async await, slower than Go, node modules, deployment not as straightforward, single threaded execution
Rust: async await, long compile time, high and long learning curve, on average will take longer to develop in Rust, sparse talent pool, faster than Go BUT for the effort it takes to get there, don't think its worth it and the extra performance is mostly not worth the above trade-offs (unless you operate on the scale of Discord)
Yes there are issues with Go. No doubt. What it gives you by default is a language designed for the web backend. No contest in my opinion.
I recently (like, in the last couple of weeks) picked up a Go project from another team at work (coming from a largely PHP background commercially, but with a lot of dabbling in other languages like Swift in my spare time).
I’ve enjoyed my initial foray into it so far. I like how small the surface area of the language is. I felt comfortable reading the code and starting to make changes within a couple of hours. While I love fancy complex features as much as the next nerd, there is something to be said about not having enough rope to hang yourself, especially when working in a team.
I like having standard first-party tooling including a formatter. Goroutines seem super neat, as do channels. People say Go’s generic system is a little tacked-on, but PHP doesn’t have one at all and Swift’s can get quite complex, so I’m happy with it.
I did find myself disappointed by a few things though, mostly around the type system. People consider PHP to be an untyped mess, but these days it actually has a reasonably expressive system that offers a lot of guardrails when paired with a static analyser. Swift has probably my favourite type system of any language I’ve used. Some complaints, in no particular order:
I think zero-values are a mistake when you have a compiler that could make usage of uninitialised variables a compile-time error (Swift does this). I want the compiler to yell at me when I forget stuff, rather than just rolling with it until it inevitably causes some logic error somewhere.
I sorely miss sum types and the ability that affords to strictly model a domain. It’s all well and good having linters that complain when I don’t handle an error, but I’d much prefer a compile-time guarantee that the return value of this function is a Value OR an Error, with the compiler forcing me to handle both cases. Granted PHP doesn’t have this, but it’s also a nearly 30 year old language that grew out of a simple templating engine. Swift is of course militant about this; you can’t even write a switch statement without handling all cases.
Further to the previous point, there doesn’t seem to be a particularly ergonomic way to model optional values, besides using pointers and checking for nil. While PHP doesn’t actually enforce that you check before doing something with a value, nullability is part of the type (if one is supplied). I can define my function to take an `?int` in order to accept null as a value, but if I define it with just `int`, I am guaranteed to not receive null (albeit at runtime, because there’s no compilation step). Swift of course has the Optional enum with the associated syntactic sugar to make this very nice to deal with. It would seem that in Go, I would need to add `if ptr != nil` everywhere to have the same sort of guarantee, though I am very new to the language so there is perhaps some better way of handling this.
No ternary operator…I accept the argument that nested ternaries are a readability nightmare but come on guys, given the infrequency in which they pop up in real-world codebases it doesn’t justify the visual noise that is “var-if-assignment-else-assignment”.
Anyway, despite my griping it’s a neat language, just a shame the designers seem to have deliberately ignored so many advances in type systems over the last few decades.
Language bashing is a sign that this language is popular.
People don't bash languages they don't care about, only languages they have to work with when they would have picked a different one if they made the call.
Naah, in my observation it is kind of bashing which is almost always around the fact that Go is not Rust, which amazes me each time why certain individuals do that instead of simply coding in Rust or writing more about Rust.
(Funny that it applies almost exclusively to Rust folks; I never saw any negativity towards Go from Zig folks, for example).
These individuals will of course downvote this comment as well as my previous comment, because they are so deeply emotionally tied to their choice of language that they hate even mentioning the mechanism; but I don't care about my points here, instead I care much more about the truth and honesty
I really love the fact that most of the time people from the Golang community would like to say "look what I've made with Golang!" That's the sign of success: the users (here I mean general devs) should not care too much about the language per se, they still have applications to ship!
Rustaceans tend to talk more about cool new language features. They are critical to what Rust does, but maybe they have also got too much attention.
> Go is not Rust, which amazes me each time why certain individuals do that instead of simply coding in Rust or writing more about Rust.
As I said: typically those devs want to use Rust but their team or employer decided to go with Go instead. So they keep writing Go wishing they were writing Rust instead (and maybe secretly hoping to convince their colleagues and bosses to switch to Rust).
So it happens precisely because Go is popular and get selected for those projects.
I took every opportunitiy to hate on Java before I even had to work with it. I then had to do CI/CD and release management with all the bells and whistles for a lot of Java projects.
and Go still not good fit neither for graphics, nor ML, no scientific computing, nor data engineering, nor embedded devices, nor VR, nor games, nor self-driving, nor industrial automation, nor robotics, nor video processing, nor desktop applications, ...
As far as languages go, Go is relatively well-funded. You can of course imagine hypotheticals where there was more money behind it, but on the spectrum of PLs it's got more support than most. And further, I think the core developers who work on it like rsc and bradfitz are objectively great implementers. Despite that, the majority of complaints about Go are a lack of language features; you even see some in this thread.
In other words, a language with an above-average amount of effort and ability and 14 years behind it is still perceived as feature poor. But imagine how much more time it would have taken if they had taken on a larger scope, like if they had gone through all the complex design process for generics up front.
For me it is a great lesson on how important reducing scope is for shipping, and also how a reduced scope enables you to more judiciously choose what is important. If they had decided that features X, Y, Z were table stakes for their launch they would never have had the time to do the other things that I think have made Go great.