I can't count how often I've seen this:
Go: panic: runtime error: invalid memory address or nil pointer dereference
I can't count how often I've seen this:
Go: panic: runtime error: invalid memory address or nil pointer dereference
I even wrote a blog post about this very thing:
https://wakatime.com/blog/48-go-desperately-needs-nil-safe-t...
Nullable types allow devs to write safe functions (functions that don't allow nullable types as input) and spread that safety through the whole codebase. That safety isn't even an option without having nullable types. Nullable types also mean the compiler knows and warns devs if they forgot to check for nil. There's really no reason why Go sholdn't have nullable types.
Don't you mean non-nullable types? Go does have nullable types, everything is nullable.
The blog post you linked calls them "non-nil types".
Like, if you already had type X that could be null, and you change the language so X can’t be null, now you need the type Nullable<X> (often written something like X?) to let it be null again.
C# and Swift are examples of this evolutionary process.
- Use factory functions for struct initialization where members are pointers.
- Use errcheck and friends to ensure errors are not ignored.
- Never dereference the left hand return value if the right hand is an error.
This itself is a very concise argument for exceptions. Automatically checks errors are not ignored, and don't allow access to the left hand return value.
Making bubbling up even easier because people find this too verbose is the original mistake:
if err != nil {
return err
}
I find it verbose too. But if the alternative is to design something that allows you to ignore it 99%, and then put a huge syntactic penalty into doing anything other than bubbling up, then people will just bubble up 99.95% of the time, and complain about the other 0.05.I'm all for ensuring errors are not silently ignored. That's Go's mistake. Exceptions are not the answer.
Maybe you're not like that, but you're the exception that proves the rule!
This is exactly what I meant. It's "handling", when viewing the consuming function as failing system. It's not "handling" when viewing the application as failing system.
I don't even particularly like exceptions either, I just like having to implement them by hand even less. Meanwhile, all the Smug Lisp Weenies™ are chuckling and mumbling something about "restarts".
What really kills me is that Go has a structured exception mechanism with panic/recover/defer, and it's actually better than try/catch/finally at that. It just doesn't seem to have caught on as the standard exception handling idiom.
Not only that, but there is heaps of code which is correct in the presence of errors (after all, Go developers tend to obsess a lot about them), but leaks resources in the presence of panics. Those just fly under the radar of most Go programmers.
Good thing Go introduced defer. Because the panic-safe era (similar to the exception-safe era of C++) seems to be an era yet to come for the Go ecosystem.
Which is exactly what you should do if you are in the game to build reliable complex systems. See my lengthy post here https://news.ycombinator.com/item?id=39808367
And yet, this is exactly how you learn to design dependable systems. You try to stay in fail early territory as long as possible (bubbling up errors, failing every system level on the way), until you reach a layer where fail stop is not acceptable (or log and throw is adviseable since you reached a top level system layer).
It's at this point where you introduce a resilience layer, sometimes using a library like failsafe.
The most horrible designs and buggy systems I've seen in my career where the result of trying to mask errors in random places, making it impossible to reason about any of the code, and leaking resources left and right. The most stable systems I've seen started from a place where _everything_ that went off script resulted in the most horrible stacktraces, but taking proper care about cleaning up resources, and improving from there. Often, the top level resilience layer would only be configured for retries or failovers in (pre-) production systems, so developers and testing would keep getting battered with stacktraces, providing a chance to fix stuff.
Maybe you could argue Rust does because it refuses to compile a ton of invalid cases, but I consider the time it takes me to write usable code (whether that's compiling or passing some runtime tests) an important metric.
You'll notice the go stdlib uses pointers for things which absolutely should not be nullable types, like '(*big.Int).Add(*big.Int, *big.Int)', or 'time.NewTimer() *time.Timer'.
'time.NewTimer' will never return a nil. 'big.Int.Add(x, y)' requires all of those things to be non-nil.
However, those are all pointers for unrelated reasons, like mutability, and so clearly pointers are not _just_ "nullable types".
I want to use values over pointers for as much as I possibly can; the sort of stuff I work on is not so performance sensitive that it would be a problem. But zero-values are totally insufficient for modelling optional values, especially for numeric data.
Now it has generics I could implement my own Optional[T] type, but backup from the compiler forcing me to check a value exists before doing anything with it is what really makes optional types shine in languages that have it.
It definitely wasn't due to lack of previous tested technologies with field experience since the 1970's.
If Golang starts to add more and more features, it will lose this advantage and become just "yet another language". But worse: because of how was Golang designed, it will then end up worse than, say, Java. In other words, it will lose its advantages without really gaining much.
The problem is that this is probably inevitable, because the people that start as inexperienced developers with Golang will grow and become unsatisfied with the language features. Some more, some less, but that will be the general trend. And I think they are already vocal enough to push the things you listed.
Instead, I think it would be better for everyone if those people would instead switch programming languages and leave Go as is. Otherwise the cycle will repeat with the next Golang...
That a language is easier to use, doesn't mean it can't add features. The features are relative to how it helps the language be more useful and solve issues that users are having, so keeps it easier to use. Languages are always going to have pressure to adapt.
And the more features a language has and that a codebase uses, the longer it takes for someone to learn it and be productive (unless they can transfer knowledge from similar concepts of other languages of course).
Do you have a citation for that? As far as I remember that’s as most a second order effect -- they wanted a language that felt as clean and straightforward as C, to benefit everybody including experienced developers, not primarily inexperienced ones.
I do indeed have a citation for that by Rob Pike - and no matter how downvoted I am, it stays a fact:
> 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.
(https://bravenewgeek.com/go-is-unapologetically-flawed-heres...)
and also:
> Programmers working at Google are early in their careers and are most familiar with procedural languages, particularly from the C family. The need to get programmers productive quickly in a new language means that the language cannot be too radical.
(https://go.dev/talks/2012/splash.article)
> As far as I remember that’s as most a second order effect -- they wanted a language that felt as clean and straightforward as C, to benefit everybody including experienced developers, not primarily inexperienced ones.
No, it's not a second order effect. Or rather: both what you say (a language "as clean and straightforward as C") as well as a language for devs early in their career (i.e. inexperienced) are both second order effects from the underlying reason that the language is targeted at developers at google.
Says anyone that has never tried to write portable C code during the UNIX wars days, or deal with UB across compiler toolchains.
Perhaps you're thinking of sum types? They are often confused as enums.
Go read Algol 60, Pascal, Modula-2, C manuals to understand what enumerations are all about.
Not a comic dance between iota and manually written constants.
From the GNU C manual: "An enumeration type represents a limited set of integer values, each with a name."
Which is exactly what Go provides. Which isn't surprising as it is identical to C in that regard.
this hack
type Operation int
const (
Escalated Operation = iota
Archived
Deleted
Completed
)
has nothing to do with typedef enum {Escalated, Archived, Deleted, Completed} Operation;
Only someone that can't get type systems would ever assert they are the same.Literally "Go enums suck", https://news.ycombinator.com/item?id=39564737
Enums suck, period. That's why many modern languages don't offer enums at all, providing sum types instead, which more accurately model what most people are trying to use enumerations for. It is an antiquated pattern that may have served a useful purpose in the early days of computing, but we've learned a thing or two about programming since then.
Well, at least you've come around to recognizing that Go does have enums, even if you needed someone else's words to say it. After all, Go enums can't suck if it doesn't have them. But, of course, Go purposefully tries to be antiquated. That's kind of its whole deal. Naturally it is going to have enums to really drive home the antiquatedness.
Unfortunely Rust wasn't mature enough in 2013, otherwise the main programing language of most CNCF projects would not have been Go.
Rust started as a research language by a PL guy working on his free time, he wasn't even suspecting his work would end up becoming mainstream. Rust only became somewhat serious several years after Go was released and used in production at Google. If you count Graydon's first Rust publication, you may as well count all of plan9 as part of Go's history! (Or how about using Go's repository first commit[0] if we're being facetious)
Also, implementing proper generics should have been straightforward from the start for Go, given that it was already mainstream in multiple programming languages back then (including the super mainstream Java and C#, with different approach). But Go was made by people who believe syntax highlighting is useless, of course they decided against generics…
[0] https://github.com/golang/go/commit/7d7c6a97f815e9279d08cfae...
-- https://go.googlesource.com/proposal/+/master/design/go2draf...
CLU was invented in 1976, C++ concepts were proposed in 2005!
Could they have done this earlier, in principle? Sure. But at what cost? What other balls would have been dropped? I think they were absolutely right to focus on aspects of the language design and tooling that they were confident about. Go is all the better for it. It's the polar opposite of a language designed to appeal to programming language enthusiasts; and well, that shows in the reactions.
By all means use Rust. I use Rust sometimes too, and it has its advantages over Go. Personally, I find generics in Go an occasional convenience and missed them only a little before 1.18.
Null as a language concept is the issue since it's inherently flawed.