type Human struct {
Name string
MaybeHasCat *Cat
}
type Cat struct {
Name string
}
func main() {
h := Human{}
h.MaybeHasCat.Name = "Taffy"
}
And boom. Null pointer exception because MaybeHasCat is null. The fact that go doesn't let you define an optional type is such a pain. A lot of newcomers while getting trained up fall for this bug.edit: updating to add the pointer i missed. also adding a playground link: https://go.dev/play/p/izcod8xF7ZQ
Optionals are just another outlet for "best practice" bullshittery and not much else. If go doesn't have, sounds like they're following YAGNI principle
I am not sure what you mean by the first point but what I am saying is that the compiler doesn't stop you from having a basic runtime null pointer exception because of "simplicity" and "yagni".
type Human struct {
Name string
MaybeHasCat *Cat
}
As you have written (not making MaybeHasCat a pointer), go will correctly initialize (at least as of go1.20.5) the nested Cat structure and allow for direct assignment.Having a null pointer exception for a null pointer is a behavior I would expect. Having one for a non-pointer structure would be surprising.
Edit: should do more than just suggest the fix is easy
So for pointer types you have two options IMO: nil checks, or getters that do the nil check for you.
I think a map type instead of a string would also make your point more strongly because your zero value will fail on access. Which yeah that sucks to run into, and I'd agree actually that a lot of Go painpoints pop up early. I appreciate that now, because most of my foe stubbing was done inside the first few weeks, but it is undeniable imo.
Yes I agree there are patterns to solve for this but it is just strange that the compiler doesn't stop you from making these errors. I mean even Typescript has:
interface Human {
name: string
cat?: Cat
}Since it isn't an error to have a nillable type, and it isn't an error to access a field, it makes sense that the compiler doesn't error when you do that thing. If you want a warning when doing something silly then you can setup a linter.
If your first thought is that is not very beginner friendly then I agree with you - I edited my original response. But otherwise there are solutions to these problems.
If that doesn't suit you and Go isn't sweet enough for you, well, fair. Its rather austere. I don't think it is a categorical error for the project (though there are features I would also like for asking).
Sure, I'd agree it feels like a whoopsie, I dislike nil values because they are a common source of faults.
But it isn't. It is, however, a better Python (IMHO).
The reason it's not a better C is that it's a garbage-collected language and it has a runtime overhead. As such, Go doesn't really have predcitable performance. It's easy to blow up memory usage (as it is on any GC language) eg goroutines.
So Go just doesn't suit a typical systems or embedded application. Yes, you can use a subset of Go to avoid dynamic allocation and GC in general but really, what's the point in that?
Why it's a better Python (again, IMHO) is that Python has all the same negatives but has a few more, most notably that it is dynamically typed or, as I like to put it, you need to write unit tests for spelling mistakes. I've come to abhor dynamic typing as a truly horrendous false economy. Go doesn't have that problem. Go does need to be compiled but it's a simple language and that compilation is incredibly fast.
My one criticism is that I find Go's unbuffed channels to be a less elegant coordination mechanism to cooperative async/await in other langauges. But YMMV.
Some evidence to back this up is that, at least while I was still at Google, Go projects on google3 were cannibzlizing Python projects and nothing else really.
My issue with Go is that it's not low level enough to be good for systems programming but it's not high level enough to avoid boilerplate.
That's really interesting because in general I find Go faster to work in than Python. Python has great flexibility but even production code I read is littered with noise that I don't see with Go applications. Other than error propagation - which is more a style choice than boilerplate - I find doing something in Go results in fewer lines of code and less spaghetti logic.
I'm a bit too dug in on Rust to spend a ton of time replacing it with Zig and my use cases frankly could be handled by a Go app without much worry about performance. But one thing Rust has is boilerplate out the wazoo.
Not that I haven't seen messy Python code (I sure have). The best Go code is gonna look better than the worst Python. But on average, it seems like complicated flows can be written very elegantly and readably in Python and look ugly in Go.
I'm sure Rust folks feel exponentially more safety than Go at compile time, but the jump from Python to Go is dramatic for me.
Go is compiled language for people coming from dynamic languages. The type inference is good enough and the compilation types are also quite fast.
I've always understood this claim as a particular (rather restricted) interpretation of "systems" code - meaning network servers, basically (maybe also CLI applications?)
I'd always thought of "systems" as including things like writing standard libraries, kernels and embedded code. But I guess there is a decent slice of other systemsy stuff that Go is good for.
Whereas something like Zig feels like it could really be a better systems programming language in general that C, in all of its different application areas.
This is basically the retcon that the golang community has settled on but it's frankly not true.
When go was originally being promoted it was heavily advertised as a systems language suitable for writing low-level programs. Nowhere was it caveated that "systems language" means something different from what everyone understood a "systems language" to be at the time. Only when it more or less failed to gain traction in that space did this get redefined to mean "network servers that mostly just shuffle bytes around and CLI applications".
It probably has changed since, it has been 10 years now, but my feelings were at first 'wow! This is easy' during the prototyping phase, then 'ugh, i'm so limited' during the finalization (and at random points in the middle too probably, but I was fanboying at the time so I didn't really notice). One of the worst thing is the debugger imho. I _get_ gdb. I haven't written a line of C in years, you put me in an interactive gdb, I'm home. Go, I didn't understand how to debug, I had to use my 1rst year technique of printing everywhere, except I never managed to make use of %p, so I was even less effective than that.