Fourteen Years of Go
go.dev
go.dev
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.
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.
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?
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).
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.
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?
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.
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?
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.
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.
This is not a general "wouldn't it be nice of Go had..." thread. We are talking about a specific problem.
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.
Like we established in my very first comment, well before you even showed up, the handling forgetfulness problem exists for all types. That would include a hypothetical (tagged) union type. I did question if you were using some other definition of sum types, as there isn't a great consensus on what sum types actually mean, but you did confirm you were thinking of tagged unions...
But it must be that you didn't actually mean tagged unions. What is the definition of sum types that you are using here that would help with the stated problem?
func foo() error
With the programmer forgetting to handle the error: foo()
Now imagine that Go received some kind of hypothetical Result type. The foo function is updated to use it: func foo() Result[error]
But the problem remains. The compiler still isn't going to tell you that you forgot to handle the error.That's what the original commenter said. Logically, how else could you forget to handle the error? The compiler will bitch and moan if you do capture the result and then don't do anything with it. You did take the time to read the thread before replying, yes?
> This is not at all a relevant issue
It must be, else how do you explain the many comments you wrote trying to explain how sum types solves the problem? Even while I continually told you they don't.
> Why are you discussing whether Go's error handling needs improvement if your imagined programmer can't even program?
I said that it doesn't need improvement because in practice your tests will catch the problem immediately anyway. I even said that it is probably fundamentally impossible for there to be technical solution to the problem.
And the rest of the time was telling you that sum types cannot solve the problem, despite your constant urging. I am still not clear how you think sum types solve the problem.
> Well, I've spent enough time on this thread.
Are you sure you spent any time on this thread?
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.
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?
How? What exactly makes this "too easy"?
In regards to a functions error returns, there are exactly two things a caller can do
func foo() (int, error) {
// foo code
}
// explicitely ignore the error
foo()
val, _ := foo()
// handling the error
val, err := foo()
_, err := foo()
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.
"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.
Instead you'll have to dig deep to undershnnd what "Add()" is doing on this particular line. How is that any different?
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.
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.
really? source? My counter-assertion is that I'd guess most code is basically never read - most of it is in ad-hoc PHP CMSes or VBA scripts.
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.
Come on HN, you're better than this.
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 thought this was common knowledge. They made whole conference talks about it.
I searched around, but didn't find anything. Perhaps the title is something different than "go minimal feature set".
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
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?
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.
> 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.
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).
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.
> Go has no standard
Here is the official spec of the language: https://go.dev/ref/spec
Which is a de-facto standard, even according to this listing: https://en.wikipedia.org/wiki/Comparison_of_programming_lang...
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.
Just saying, Go is far from the only language that values backwards compatibility.
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.
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.
If you wouldn't mind reviewing https://news.ycombinator.com/newsguidelines.html and taking the intended spirit of the site more to heart, we'd be grateful.
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.
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.
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.
> and they say goodby to Go as a result? and they say goodbye to Java as a result?
> and maintain Python 2.X indefinitely.. Someone said that? Anyway 20 years of 2.x branch is pretty impressive.
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.
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.
If it’s good enough for Kubernetes then it’s good enough for any business.
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".
lol
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
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.
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.
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.
Thanks, authors and community.
Are you sure ? I don't get that feeling from HN at all.
Sorry to be a pedant, but ReasonML was developed to make OCaml accessible to developers with JavaScript experience.
Not in the sense that it's a bad language, but just that it's not used much anymore.
https://trends.google.com/trends/explore?date=all&q=%2Fm%2F0...
(Funnily enough, it still absolutely dwarfs Rust in Google Trends, which highlights how much HN, Reddit and such are echo-chambers)
I have no problem with language-fans promoting it and singing praise to it. I do the same with languages I happen to like.
What I do have a problem with, is evangelizing.
https://gist.github.com/graninas/22ab535d2913311e47a742c70f1...
What are you talking about? Go is a popular language. Many of the successful software projects released in recent years are written in Go.
"Choose Boring Technology": https://boringtechnology.club/
It actually worked, remarkably, but I wouldn’t trust it on production.
Software is either maintained or abandoned. There’s no “done”, as much as we want it to.
funny enough i have a simple but old app created with “create react app” that doesn’t run on anything other than node 16 and below.
In the case of the "internal dependency" one, plenty of people regularly patch stuff in NPM and keep the same version number.
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/.
Recreating the whole outdated stack is often doable, but it's really time consuming and often feels more like archeology than Computer Science.
What you really want is strong backward compatibility so that Node "30" still manages to build projects from 10 years ago.
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.
In go, code from a decade ago should just work, unless the underlying system changes too much.
Your old Go code not only will work with new versions of the compiler - it probably will run faster.
>But then you have to keep old interpreter installed
...thanks to `pyenv` that really is a solved problem, even outside containerized environments.
Still compiles is not the same thing as "works".
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.
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).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?
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.
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.
With static you don't need that environment, only the kernel, so yes, static compilation solves the problem.
https://github.com/golang/go/issues/39568
As far as I know every other language standard library still allows CN to be used to validate the hostname when the SAN field doesn’t exist.
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.
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.
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.
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.
---
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.
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.
- Accessing array out of bounds: panic
- File not found: error
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. package url
func Parse(rawURL string) (*URL, 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.
No, absolutely not!
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.
> 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.”
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 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.
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.
The one option he doesn't have though: Continue running the car as-is. And that's the point I am making.
> and a programming language that purposely allows a library to do so as a design choice is also defective by design in my opinion.
You are entitled to your opinion of course, but I'm not going to agree, and it seems a lot of people are completely fine with that design decision: https://madnight.github.io/githut/#/pull_requests/2023/3
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.
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.
> You are entitled to your opinion of course, but I'm not going to agree, and it seems a lot of people are completely fine with that design decision: https://madnight.github.io/githut/#/pull_requests/2023/3
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.
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.
A quick google turns up https://go.dev/blog/defer-panic-and-recover, which says:
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?
And another quick googling turns up this:
https://github.com/golang/go/issues/26799
which says:
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.
It did not though?
- threads implicitly catch_panic at their boundary, and convert that to a `Result` which you can receive when you join on the thread: https://doc.rust-lang.org/std/thread/type.Result.html
- 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:
- sync: https://play.rust-lang.org/?version=stable&mode=debug&editio...
- async (tokio): https://play.rust-lang.org/?version=stable&mode=debug&editio...
How is a panic crashing a program a bad thing?
Panics are not Exceptions. They usually are the result of something happening that should crash the program.
Unless you're working on a CLI tool that is short lived anyway (and represents a lot of projects using Go).
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.
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
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.
Bad code is bad code. Bugs are bugs. This issue is not a major source of problems in Go code.
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.
---
[1] https://www.infoq.com/presentations/Null-References-The-Bill...
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.
[0]: https://github.com/golang/go/blob/master/src/go/parser/parse...
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.
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.
The stack I settled on:
Postgres + sqlc
templ (kindah jsx for go) + htmx
Barely have to write any js/ts now
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
}
}
But it only got that recently.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.
Interviewer: What would you say if man walked in here with no shirt, and I hired him? What would you say?
Will Smith: He must have had on some really nice pants.
Go has some really nice pants i.e Simplicity with good enough performance, backed by big corp.
Simplicity scales!
Java, Python, C#, Javascript, PHP, C, C++, Ruby, are all from the previous century.
There is Swift that is roughly as old as Go and Rust that is more recent, but they're not nearly as popular as Go.
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?
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.
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.
The flexibility of the language and its syntax. The more constructs are syntactic, the less minimalistic it is, and Go is a very syntactic language.
> I've always considered Go to be minimalist in terms of available tokens to the programmer: https://github.com/e3b0c442/keywords/blob/main/chart.png
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.
You're not making any sense, and you're not providing any sort of argument either.
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".
> Minimalist isn't restrictive.
No, it's not. And neither is the reverse.
And go is restrictive, not minimalist.
And because this is HN, I have to mention of course you can over engineer current Go as well. But it's not "fun".
I've been programming in Go professionally for 6 years, and it covers 80% of usecases well, but that last 20% is really tricky
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:
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.
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:
- https://github.com/ahmetb/go-linq: modelled after LINQ, but created pre-generics so only recently added some generics features
- https://github.com/samber/lo: kind of like JavaScript's lodash library but for Go 1.18+ (with generics)
- https://github.com/asynkron/gofun: simple library with stand-alone functions to filter, map, and so on
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.
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.
Thank you go developers for embracing TAB.
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 ?
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.
Rust's learning curve is waaay steeper than that
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.
If your issue is that the syntax is too complex then you've got a problem since semantics are usually way more complex to grasp than syntax.
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.
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.
I use many tools that are written in Golang, and I am very grateful for it.
docker, duf, chezmoi, fzf, croc are few tools I use that are written in Golang.
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.
It is 100% possible with Java.
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.
* It's name
* Nils
* Pattern matching
* Enums / Sum types
* Generics
* Error handling
Other ecosystems have recognized that pattern matching and sum types are the way forward as well as null safety.
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.
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.
And it performs very well, certainly in comparison with Python.
Oh come on, who doesn't love to see something like this?
[print("fizzbuzz" if not x % 15 else "buzz" if not x % 5 else "fizz" if not x % 3 else x) for x in range(1, 101)]Python has horrible builtin tooling and Go has great builtin tooling.
Go is very restrictive language, while Python is not.
Go has horrible FFI support, while Python has some of the best FFI support.
I think the only thing that is similar between them is both of them are easy to understand compared to most languages.
I mean you don't see people building AI/ML, Data Science lib raries in Go for a reason.
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.
In practice though, there is a reason why Go is seeing continuous growth in usage, so I kinda doubt that these features of Go are subjective.
> But other modern languages like Rust or even Swift are quite a bit better on that side of things, and it shows.
In what metrics does that "show" if I may ask? Because it isn't showing in usage numbers across projects.
Perhaps these features you speak of don't matter as much as we think.
Us mere mortals are trying to pick up Thor's hammer at the end of the day.
In the words of Rob Pike:
"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.
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.
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.
Rather than being explicitly designed for problems Google was facing, it took a fair amount of time to find a niche (initially, log processing).
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.
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.
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.
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.
In case of option/result a single if/else is basically the same as if err == nil… I’m sure an ergonomic syntax could be found.
Erlang/Elixir would like to have a word ;)
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.
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.
It’s called Java
[Warning super hot take]
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’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.
Doesn't Google owe a couple hundred million in royalties for the name to the ancestors of the Chinese and/or Indian people?
In my estimates N = 6, more less
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.
Now I also actively hate on its ecosystem.
(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.
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.
― Bjarne Stroustrup, The C++ Programming Languageand 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, ...
[1] not a good one.