I have both production systems (ircrelay.com) and well-used client software (packer.io) written in Go, so I'd like to comment on this from both perspectives, server-side and desktop-side.
First off, both from the get-go have been extremely reliable. IRCRelay has a custom IRC router that is able to route IRC connections to their proper bouncer server, then just stream the contents to and from. This server was written once, and has been running in production for over a year without a single downtime incident or crash incident.
Likewise, Packer reliability has been astounding, even to me. It really rarely crashes, and when it does it is because I'm usually skirting around the type system of Go (casting).
The PRIMARY reason Go is so reliable is also a reason many people hate go: you _have_ to handle every error. There are no exceptions, you know if a line of code can fail or not, and you have to handle it. This leads to a lot of "if err != nil" checks, and it can be really, really annoying sometimes. But the truth is: once you get that thing into production, it is never going to crash. Never. MAYBE nil slice access, but that's about it. Note the compiler doesn't actively enforce this, but it is Go best practice to handle every error.
(Pedantics: yes, you can assign errors to "_" variables to ignore them or just not accept any return values, but this is very actively discouraged in Go and it is one of those you're-doing-it-wrong kind of things)
Also: a compiler. Coming from Ruby, having a compiler is just amazing. Obviously compilers have been around forever and Go's isn't special in any way. But after living in a dynamic language heavy environment for many years, I can't imagine going back to not having a compiler. It just makes writing new features, refactoring old ones, etc. all just so easy. It catches all the bone-head mistakes that cause a majority of crashes in code I previously wrote.
Another reason: Testing is heavily encouraged and baked right into the official build tool. This makes it so easy to write tests that you always do. It isn't really an opt-in thing, because it is one of those things you just DO if you write Go. This makes it so that even with explicit error checking and the type system, you're fairly certain your logic is reasonably correct as well.
And finally, you're statically compiling your applications. Once they're running, no external dependencies can mess that up. Dependency management from _source_ form is a problem with Go, one that is actively acknowledge and being worked on. But once that program is compiled, it is safe forever as long as you run it on a proper kernel.
So my experience is anecdotal, but there are real language features and community ideologies at play here that make Go a really stable language to write programs in.
> The PRIMARY reason Go is so reliable is also a reason many people hate go: you _have_ to handle every error. There are no exceptions, you know if a line of code can fail or not, and you have to handle it.
> Pedantics: yes, you can assign errors to "_" variables to ignore them, but this is very actively discouraged in Go and it is one of those you're-doing-it-wrong kind of things
The _ thing is fine because it's visible, but what's potentially insidious is the compiler having no complaints about:
I do like Go a lot, but this is a valid asterisk on talk of the safety of its approach to errors.
---
For non-gophers: The reason this doesn't come up all the time is that functions often return multiple values, at least one of which you actively need, at which point you are forced to do something with any error that comes along for the ride.
True, but I consider this the same idea. If you look up the API of a method and see an "error" return type, it is your responsibility to handle it, and you're very encouraged to do so.
It won't compile as `os.Create` returns `(file, error)`. So you must either:
// Proper error handling
f, err := os.Create(fname)
if err != nil {
log.Fatalf("Could not create '%s'", fname)
// Well may want to do something other than crash.
}
defer(f.close())
If you ignore the error it's obvious
f, _ := os.Create(fname)
defer(f.Close())
It's like doing this in Java
File f;
try {
f = new File(fname);
} catch (Exception e) {
// Ignore Exception
}
Possibly worth pointing out to non-gophers: in `Go` it is illegal to declare an unused variable.
So if you write `f, err := os.Create("/bad/path");`, and that is the first time `err` has been declared & used in that scope, you can't just let `err` go unused or your program won't compile.
The PRIMARY reason Go is so reliable is also a reason many people hate go: you _have_ to handle every error.
If the compiler isn't enforcing it, you don't have to do anything. At best you can say the language suggests it, but if there isn't even a warning I don't see how that's different from pretty much every typed language.
This is actually enforced [in some cases] as a compile-time error. (BTW the `gc` family of Go compilers don't have warnings.)
The two errors in particular that help trap this are:
1. All declared variables must be used.
2. All return values must be assigned to a variable.
(Go allows for multiple return values from a function.)
---
Idiomatically one would store [one or more] return values from a function with a short-form declaration (`:=`) which declares and assigns the LHS to the RHS.
`a,err := someFunc()` where `someFunc()` returns `(SomeType, error)` declares and assigns `a int` and `err error`.
If you declare `a,err := someFunc()` and do not use `a` or `err` elsewhere in scope, your program simply won't compile.
The only way to squelch that error is to either use the variables OR you can use a `_` on the LHS. (Which makes an "assignment" to a blank identifier, e.g: discards the value.)
---
An example of where this _wouldn't_ be enforced by the compiler is if you've declared [and used] `err` elsewhere in the scope and you are _reusing_ the identifier.
For e.g: if you declared, assigned, used, and reassigned `err`. The compiler won't force you to use it _after_ the second assignment, so that second assignment could go untrapped with no complains from the compiler.
If you call a function that returns nothing but an error and you ignore the return value, the Go compiler does not complain and you will ignore the error. Go doesn't always force you to handle errors.
Also, Go has exceptions: panic and recover. They are idiomatically not used in the same way, but it's not true that you know by looking at Go code whether each line will fail, because lots of language constructs can implicitly panic. (You gave an example of one: indexing a nil slice.)
One major difference between panic and exceptions is that you can only catch it on a function exit boundary with defer and recover. There is no "try".
So really, you should think of panics more like Erlang's error handling, where a segment of the program (bounded by "recover") will just stop running, but can initiate the process of self repair.
The difference is that Erlang has process isolation: a panic is guaranteed not to mess up the state of any other process, because there is no shared state. This is what makes reliable self-repair possible. In Go, there is one giant shared mutable heap shared between all goroutines. So there is no guarantee that a panicked goroutine left the heap in an orderly state such that self-repair is possible.
Erlang actors and goroutines are very very different.
True. Although, panics do run defers, so with good coding practise it's possible to ensure that either you don't share things (but rather pass them around over channels) or things like locks are safely cleaned up.
But seriously, it's not just possible to be memory safe in Go, it's fairly easy. You basically have to remember to never lock without defer mutex.Unlock() unless it can't possibly panic, and to share data via channels such that it only ever has a unique "owner".
Personally I'd have much preferred full isolation, immutable data with threadsafe mutable references, and Clojure style "persistent" collections which can be treated as values. But Go didn't go down that route.
Sorry, note my "pedantics:" note on the bottom. If you ignore errors then you're actively going against the idiomatic way to write Go. I never claimed it was compiler-enforced. I'm not arguing for or against either side either (compiler or non-compiler), I'm just trying to state facts.
It is true that panics exist, but panics generally represent non-recoverable errors that deserve to crash. They're very rarely bugs (except slice access, which I mentioned). In practice, in any Go systems I've written, panics have not been an issue and have really never occurred (except, again, slice access, which I mentioned).
Regarding your comment below (I can't comment because it is too deep): where did you quote me saying the compiler forces you to handle errors? It doesn't, and if I said that it is an error.
I don't know how people keep misreading my comments, so in all caps: THE GO COMPILER DOES NOT ENFORCE ERROR HANDLING AND I NEVER CLAIMED IT DID (in any of my comments).
As frou_dh points out below, Go does not force all errors to be handled (edit: to be clear, they don't even have to handle it with _ ). See this working example in the playground for reference: http://play.golang.org/p/IPDTG6Ub2b
Edit: The comment that you replied to said "Go does not force you to handle errors." Your comment said "True but it's not idiomatic." My reply was meant to show that it's very easy to ignore errors in code that is not obviously un-idiomatic. Also, note that in your grandparent comment, you stated, "The PRIMARY reason Go is so reliable is also a reason many people hate go: you _have_ to handle every error." In my link, however, the error goes unhandled.
I disagree. It's not idiomatic in, say, Python, to catch every possible exception, only the specific ones that the programmer thinks are likely to occur in practice.
I'd love to see a "go vet" check or the like that complains if you implicitly discard an error return (but can be silenced by explicitly assigning it to _).
That still gives you a way to do odd things, but you'd have to explicitly choose to be odd. And since it's a check rather than a new language rule, old code still compiles.
As I don't program in Go this might be wrong, but doesn't the programmer have to tell the Go compiler to ignore the error by using the _ variable thing?
In that sense Go is still forcing the programmer to handle the error and it is the programmer who is deciding to ignore it.
I can see making the programmer do nothing more than mark the error as ignored still forces the programmer to think about the error and as such adds some value.
Um, not really. There's a working group that is gathering data, exploring options, and intends to present a plan early next year.
Right now what we have is very minimal. The current tools let you manage versions your own way. If we built something more complex we would be stuck with it forever. We're being conservative because getting it wrong would be worse than not providing it at all.
I have no idea where you get that impression from. Most people who hear about that work say something like "Oh great! I'm glad people are thinking about this, and look forward to seeing what comes of it."
There are a couple of vocal people there with strong opinions. They don't represent the community as a whole. Only people with strong contrary opinions would have anything to say in such a thread, so there's a selection bias at work here.
> Dependency management from _source_ form is a problem with Go, one that is actively acknowledge and being worked on
A bit off-topic from the original article, but I wrote a little tool for this very purpose. It's still not fully fleshed out, but we're using it at my place of work and it's worked out pretty well so far. I haven't gotten much coverage of it though, so if anyone wants to take a look at it and send some comments/suggestions, that'd be pretty awesome:
The nice thing about goat is that if you've been following go's best practices for development (namely, all of your import paths use the absolute import path, for example "github.com/whatever/myproject/submodule") then you won't have to change ANY of your existing code.
Doesn't this just come down to "because we are better programmers?"
Or maybe: worse programmers :P
A truly excellent programmer wouldn't need language support to making sure errors are handled for he or she knows no single piece of code is ever going to be reliable if error checking is omitted, no matter what language it is written in, and he/she automatically deals with it.
Then again, everybody makes mistakes and noot many are trylu excellent so a language guiding you into the right direction is certainly a Good thing. Easily proven if you look at the amount of SO questions where the answer basically is "well, if you didn't omit the error checking you wouldn't have to ask this question in the first place"
I guess I'm just a little sceptical that that is the reason. Seems a little too handwavy. Especially when so many other languages/frameworks had decent tooling, too. Consider, Rails would setup tests for you, as well. I fully grant that testing most websites is a good deal more involved than testing most utilities. But, if anything, I would suspect that is the true gem in go. Seems it is mostly used for utilities with well defined inputs and outputs.
Your 'truly excellent programmer' is like the ancient man of myth; they simply don't exist.
An excellent programmer understands that they are human, and uses tools to amplify thier abilities. Nothing about extra tooling makes you less of an excellent programmer, or extcellent programmers would only use machine language.
In every go thread I see all these favourable comments, but they only seem to ever compare to dynamic languages. Where's the comparison of go with OCaml/Haskell/Scala/F#?
Out of curiosity, what sort of setup do you use to develop Go? Do you catch mistakes like typos, etc at compile time, or do you use an IDE that would catch those beforehand?
I use GoSublime, it comes with GoCode so you get to know the method signature when the autocompletion pops-up.
Also in the settings of GoSublime, you can set it to run `go lint`, `go vet`, `errcheck` and `go build` at every save, and capture the output to put it in the guther. This way, you catch pretty much everything.
First off, both from the get-go have been extremely reliable. IRCRelay has a custom IRC router that is able to route IRC connections to their proper bouncer server, then just stream the contents to and from. This server was written once, and has been running in production for over a year without a single downtime incident or crash incident.
Likewise, Packer reliability has been astounding, even to me. It really rarely crashes, and when it does it is because I'm usually skirting around the type system of Go (casting).
The PRIMARY reason Go is so reliable is also a reason many people hate go: you _have_ to handle every error. There are no exceptions, you know if a line of code can fail or not, and you have to handle it. This leads to a lot of "if err != nil" checks, and it can be really, really annoying sometimes. But the truth is: once you get that thing into production, it is never going to crash. Never. MAYBE nil slice access, but that's about it. Note the compiler doesn't actively enforce this, but it is Go best practice to handle every error.
(Pedantics: yes, you can assign errors to "_" variables to ignore them or just not accept any return values, but this is very actively discouraged in Go and it is one of those you're-doing-it-wrong kind of things)
Also: a compiler. Coming from Ruby, having a compiler is just amazing. Obviously compilers have been around forever and Go's isn't special in any way. But after living in a dynamic language heavy environment for many years, I can't imagine going back to not having a compiler. It just makes writing new features, refactoring old ones, etc. all just so easy. It catches all the bone-head mistakes that cause a majority of crashes in code I previously wrote.
Another reason: Testing is heavily encouraged and baked right into the official build tool. This makes it so easy to write tests that you always do. It isn't really an opt-in thing, because it is one of those things you just DO if you write Go. This makes it so that even with explicit error checking and the type system, you're fairly certain your logic is reasonably correct as well.
And finally, you're statically compiling your applications. Once they're running, no external dependencies can mess that up. Dependency management from _source_ form is a problem with Go, one that is actively acknowledge and being worked on. But once that program is compiled, it is safe forever as long as you run it on a proper kernel.
So my experience is anecdotal, but there are real language features and community ideologies at play here that make Go a really stable language to write programs in.