He went to a previous commit from the one I had linked, saw my stupid attempt at estimating it, and proceeded to go on a rant in which he simultaneously called me an idiot and pointed to the answers in the runtime code. Pretty entertaining.
Of course, the code was stupid because I wrote it knowingly so, just so I could fill the function with compilable code and go debug something else. I guess not writing a comment is on me for not knowing Rob Pike was going to go out of his way to find it the next day.
Fun day at the office, that one. So yeah, I am not the greatest fan of Go or him either. But that was from way before when I had to write a JSON-LD parsing library and cast stuff to `interface{}` everywhere.
I write personal stuff in Rust now, but would happily do it in any other language with a proper toolchain and type system. Go has only one of those, so I don't dislike it. Just don't really enjoy it.
I agree that it’s not meant to be condescending.
Go is very much AT&T Limbo from plan 9, with loose ends wrapped up and refined on Google’s dime, since Google brought Bell Labs people and their projects to Google, and the quote is therefore a purposefully misleading retrofitting of a history and design predating any concern about noob hires at Google.
Garbage collection is another such example. Compare that to e.g. rust's model which hoists the understanding of lifetime to the engineer, while encumbering the compiler with the dirty work. Whereas golang asks the engineer to do neither (granted golang came later, so maybe they didn't think it was possible).
I disagree, if you don't think engineers can be trusted, you wouldn't want to trust engineers to remember to close files or connections, you'd have some language feature to do that automatically.
C++ and Rust solved this with RAII and destructors.
In my opinion, this is on the same level as "just remember to check bounds" or "just remember to free memory".
Esp. closures, which Go uses a lot all over the place, are quite cumbersome in Rust.
(I like both languages a lot)
What you're saying is true, but it's still not an obvious choice - there's still a tradeoff.
The (hypothetical, e.g. Rust) compiler can check only a subset of correct programs, which means that many correct programs can't be successfully checked. This is all good if it only affects edge cases, it's not that good if it affects common cases.
It additionally becomes less of an issue (in Go) if you avoid shared mutable state and use channels for complex cases.
So all in all I agree the compiler checking your synchronization is nice, but it's a tradeoff (at least for now), and with the current state of it I think most projects will be just fine without it. On the other hand, there are projects that should definitely use languages that allow for static analysis like that.
I would feel a lot more comfortable with channels if a linter would verify that every message is either mutex protected or a newly created graph (maybe a deep copy) never accessed again by the sender. Go already does some escape analysis to minimize heap allocations, but doesn’t flag a goroutine receiving nested maps or array slices that may be shared.
Further still, “it’s better for the compiler to check your work reliably” is limited to the shared mutable state that is completely in the purview of a single process—if you have a networked resource or a file that is accessed by multiple processes, rustc won’t save you. In Go’s niche (web services, daemons, etc), this is the overwhelming majority of all state.
Leases or transactions save us in that case. Transactional memory has been offered, but hasn’t gone mainstream.
Rust uses closures very heavily, for things like iterators, spawning threads, async tasks, and lots of other things. I've never felt them to be cumbersome at all.
Actually, for me, closures in rust are easier to use than in any other language I have used besides for various lisps!
Those work fine in Rust for simple use-cases, but it gets annoying quickly.
With a garbage collected language like Go, closures can freely capture stuff, use it, and then once no closure (or any other code, fwiw) references it anymore, it gets freed.
Escape analysis and garbage collection make even more complex (like closures that outlive the scope) use cases (where lifetimes would be hell) trivial.
Having spent ~2 decades working with engineers, I'm inclined to agree... Past, present, and future self included!