
Why Go Gets Exceptions Right - motter
http://dave.cheney.net/2012/01/18/why-go-gets-exceptions-right
======
repsilat
To be honest I don't think there's any practical difference between this and
using `errno` in C. In fact, I suspect Go may have adopted a more complicated
solution simply because we're squeamish about using global variables.

At the end of the day this solution doesn't work for "high level" code for the
same reason `errno` and return codes aren't adequate - is forces the user to
litter their code with explicit checks for exceptional cases. The whole point
of exceptions is that they let the user write the code as if everything is
peachy, to let the code bail out wherever appropriate, and to pass the buck
for fixing it to someone who cares.

(I don't know anything about `panic`/`recover`.)

~~~
Jabbles
The practical difference is that there is (partial) enforced checking of
errors. If you do not wish to check the error code, you must be explicit.

    
    
        f := os.Create("output.txt")
    

Will give a compile error

    
    
        multiple-value os.Create() in single-value context:
    

and should be written as one of these:

    
    
        f, err := os.Create("output.txt")
        f, _ := os.Create("output.txt")
    

where the second indicates that you are ignoring the returned error.

I say "partial" because if you ignore all the return values you can implicitly
ignore an error:

    
    
        fmt.Println("Hello")
    

It is quite common to see:

    
    
        if err != nil {
            //handle error
        }
    

in Go, as their philosophy is that the caller should handle the error, not an
unrelated piece of code. Yes, this does sometimes look like "litter", and it
doesn't really make for the most impressive code. However, you won't be
surprised by it, it's simple to understand, and if something goes wrong,
you'll know what, when, and what was done about it. This has been Google's
opinion on C++ for a while. [http://google-
styleguide.googlecode.com/svn/trunk/cppguide.x...](http://google-
styleguide.googlecode.com/svn/trunk/cppguide.xml?showone=Exceptions#Exceptions)
lists some pros and cons. Writing code that ignores errors is one way of
writing code, I think it's idiomatic Java and Python (please correct me), but
that's not the way that was chosen for Go.

I don't really use panic() except to indicate bugs:

    
    
        if playerCount == 2{
            go multiPlayer()
        } else if playerCount == 1{
            go singlePlayer()
        } else {
            panic(playerCount)
        }
    

However they can be useful inside libraries. For example if you call
fmt.Printf("%s",s), where s has a method s.String(), will print the result of
s.String(). Sound good? But what if s is a nil pointer? Well, you may wish
s.String() to handle that and return "A nil s", which is fair enough. However
is s.String does not handle this case it may panic(), leaving fmt.Printf to
recover() and write "<nil>" instead.

<http://golang.org/src/pkg/fmt/print.go#L630>

~~~
kstenerud
This doesn't sit well with me. Who is to say what's catastrophic and what's
not? Should I use all libraries in fear because passing a nil pointer or an
improperly formatted date may cause it to terminate the program with no
recourse? Suddenly every library is a ticking time bomb with a scorched Earth
policy for things it doesn't like. How am I to know what's going to cause my
valuable program to simply die in the middle of the night? Look at the
outdated/incomplete documentation? Pore over the complex code (if it's even
available)? Now not only do I need to litter error value checks everywhere,
but I also need to preemptively sanitize all input to any library calls I make
and pray that I haven't missed something. This is simply trading one problem
for another.

~~~
genwin
That's a potential issue with any language, I believe. In Go you can recover()
from what would otherwise be a termination. For example, I have a function
that grabs and parses RSS feeds. If my program is fed junk by the remote site,
I don't want to terminate, I want to ignore the error for that feed (after
logging it). To do this I recover() from any error at the feed level.

------
Jabbles
As much as I like Go, I don't think we need to have an article about it every
day, especially one that is several months old and contains wildly out of date
statements like "panics are always fatal".

<http://golang.org/ref/spec#Handling_panics>

~~~
motter
Thanks for pointing out that this differs from the specification.

I found this quite thought-provoking about different error handling
techniques, so thought it worth submitting.

------
pacala
I fail to see how this is any better or worse than Java checked exceptions.
You've got an error, you return an alternate value, which is handled by a
different part of the code. Here is a different syntax to semantically write
the exact same code, it's obviously _superior_.

~~~
TylerE
The difference is that the Go style can provide a sane fallback, which the
client code can IGNORE.

For instance, the built in map type returns a pair of values, the first is the
actual matched value, the 2nd a boolean. If a key is not found the value is
set to the type's zero-value, and the boolean as false. So for a map of
string->integer, instead of throwing a NoSuchKey exception or something, just
returns 0,false. So there's no mandatory ceremony when you don't care.

~~~
pacala
Sure, and you've got to check the boolean if you care. Which is almost the
same as the (gasp) Java code:

    
    
        // don't care
        value = map.get(key);
        do bleh
    
        // care
        if (map.containsKey(key)) {
          do blah
        } else {
          value = map.get(key);
          do bleh
        }
    

To be clear, there are two kinds of errors:

1\. The code is screwed up. Java runtime exception, log and hope someone is
paying attention to the logs.

2\. The world is screwed up. Java checked exception, catch and return a nice
message to the user.

~~~
TylerE
It appears your code will find the value _twice_. That is less than ideal and
not equivalent at all.

~~~
kruffin
If an assumption can be made that you won't put null values into the map then
it can be rewritten where it doesn't need to do the check described above:

    
    
      value = map.get(key);
      if (null == value) {
        // Handle no value in the map
      }
    

Any class correctly implementing Map in Java won't throw an exception if there
is no value for an input key
([http://docs.oracle.com/javase/6/docs/api/java/util/Map.html#...](http://docs.oracle.com/javase/6/docs/api/java/util/Map.html#get%28java.lang.Object%29))
and some don't allow you to put null values in to begin with
([http://docs.oracle.com/javase/6/docs/api/java/util/Hashtable...](http://docs.oracle.com/javase/6/docs/api/java/util/Hashtable.html)).

~~~
genwin
I think one of the points of the OP's article is that in Go you don't have to
code like you've done here, where a single return value means one of two
things (in your example: value in the map, or special reserved value that
indicates the key wasn't found in the map). Go is cleaner, even if it's just a
small improvement.

------
antonpug
Am I the only one who is saying "What the heck is Go and where did it come
from?!" All of a sudden, in the past month or two I am seeing a lot of talk
about Go, out of thin air. What's the rage about? Is it really going to take
over the world?!

~~~
genwin
Resistance is futile! It _is_ a slick language. It hit version 1.0 in March,
which is probably why you're hearing so much about it now. Its concurrency
model lets you keep all the machine's processor cores busy, without you going
insane; that's worth the price of admission for me.

~~~
antonpug
Do you see any use for Go in Web Development?

~~~
genwin
I'm coding a web app in it right now. Go includes a package for its own web
server. That's a huge benefit, because it means the app can be state-ful
instead of the usual (for Python, PHP etc.) stateless. The former means you
can have all the data you want ready to go, in the most efficient memory
structure, when the user arrives. Which translates to great performance. For
example check out <http://langalot.com> and somewhat slowly type in "bark".

~~~
antonpug
Do you see it as a competitor technology to things like Node.js / Meteor?

~~~
genwin
Yes. Node.js supports only a single processor core. I don't know much about
Meteor but it doesn't look like it offers concurrency features.

The concurrency in Go (and to a lesser extent in Node.js--probably) is
amazingly performant. For example, when I poll & parse 100 RSS feeds and the
slowest one takes 6 seconds to return from the remote site, the whole routine
returns in < 7 seconds. A synchronous single-core app would take up to 6
seconds * 100 = 10 minutes for the same process. In Go, when I call out to the
remote sites, each call returns immediately, so that all 100 remote calls
finish immediately. Go puts my routine into a dormant state while waiting for
the remote sites to return data. So during that 7 seconds my app may have ~5
seconds of dormancy to do other processing (in a completely independent code
path), again using all the machine's cores.

------
btilly
The big complication in Go that makes exceptions tricky is, "Who is my real
caller?"

If you're in a goroutine, is your caller the place your goroutine was launched
from, or whoever is hooked up at the other end of the channel that is
expecting you to respond? In some sense it is both. Where would you propagate
an exception? How do you choose?

Returning exception values provides an answer - it is up to you.

~~~
mononcqc
See links and monitors in Erlang as another way to do it, tried and tested for
decades in production code.

~~~
ericmoritz
Erlang's error handling is very well thought out. In fact, error handling is
the main reason Erlang is shaped the way it is.

It is a combination of Go's technique but with panics for exceptional errors.

Like Go, functions should return error values for known errors. For instance,
`file:open/2` returns `{error, enoent | eacces | eisdir | enotdir | enospc}`
for known error conditions that can be handled. Unknown, exceptional
conditions, like a NAS going down, can't be compensated for because the set of
unknown errors is unbounded. There could literally be an infinite number of
things that can go wrong that you don't know how to handle. For try/catch
style error handling, the only safe way to handle the unknown errors is to do
a Pokemon catch clause globally or around every single potentially error
causing function call.

Because of the fact that you can't stop errors from happening is why Erlang
has processes. When an unknown error occurs, the individual process dies and
anyone monitoring that process is notified. OTP has supervisors whose sole job
is to watch over processes and restart them if they crash.

Any state that needs to be preserved between restarts is stored somewhere safe
(Erlangers calls this the "error kernel") so that when the process comes back
it'll resume where it left off.

Once you structure your code to "Let it Crash", your code gets much simpler
and safer. #1, the error return values forces you to compensate for known
errors that can happen with a function, #2 you have to think about data
durability in order to "Let it Crash". #3 Supervisors take care of the rest.

Data durability is made much easier with the fact that individual processes
are single threaded and data is immutable, this makes them transactional by
nature.

------
fafner
I think the language that gets Exceptions right is Common Lisp because of the
restarts. With other languages handling exceptions is complicated. You either
need to know internals of the code you are using or you simply bail out and
print and error. With restarts you can simply offer ways to fix it and keep
the logic where it belongs, close to the "throw".

------
mononcqc
the timeline C -> C++ -> Java -> Go is ridiculous at best, and this ignores
work on exceptions or error handling in countless other languages that could
have made it better.

~~~
batista
Wrong, the timeline C -> C++ -> Java -> Go is more or less what the industry
went through (well, assuming it's getting to Go now and it'll catch on).

It correctly ignores the work on "countless other languages", because they
didn't have any if at all traction to the trade, and don't matter much as far
as 90% of programmers are concerned, and the few languages outside those that
do (Perl, PHP, Ruby, C#, JS, Python, VB) are quite similar to the above.

------
usefulcat
The primary question to answer when choosing between using error codes or
exceptions for signalling errors is this: do you want the error propagation
code to be written by one or more humans, or do you want the compiler to
generate invisible but consistent error propagation code?

------
bitbiter
The obvious problem with the multiple return paradigm is that Go returns:

result AND error

whereas it would make much more sense to return:

result OR error

After all, if the error is true the result is invalid. This is one thing
exceptions basically gets right. Go, much like the errno paradigm, relies on
the programmer's diligence to know when it is safe to use the result. A better
designed language takes away the option of accidentally doing something wrong.

Besides exceptions, this better approach can be accomplished by returning a
union type variable that knows its current type (languages outside the C
lineage call this sum types or tagged unions).

    
    
      var foo = Bar();
      if( typeof(foo) == Error ) {
        // handle error
      }
      else {
        // result is safe to use
      }

~~~
TillE
Conceptually that makes sense, but having to explicitly check the type of the
return value every time is just hideous.

~~~
pacala
Uhm, then don't code in Go?

~~~
sirclueless
You can ignore errors in Go by assigning them to _. You can't if your return
value is sometimes an Error.

~~~
ori_b
You could implement something like lvalue pattern matching, if you wanted to.

Assuming you construct tagged unions like so:

    
    
        Valid 123
        Error "Error message"
    

You could pattern match on the left side of an assignment like so:

    
    
        Valid foo = dosomething()
        use(foo)
    

Which would extract the wrapped value into 'foo' if the union's tag was
'Valid', or aborted/panicked/did something else somewhat sane if the value
didn't match.

------
dllthomas
I find it interesting that they express result and error as a product rather
than a sum.

~~~
Evbn
There is a mailing list thread somewhere that explains that the Go designers
did not understand the difference between sum types and untagged unions, and
hence inappropriately rejected sum types.

~~~
dllthomas
That explains it. Interesting.

------
cpeterso
Go's error handling reminds me of the Icon programming language's elegant
"goal-directed execution". Icon's control structures use implicit
"success/failure" instead of true/false boolean logic. For example:

    
    
      if a := read() then write(a)
    

read() could return a false or zero value and still "succeed", calling
write(a). If read() returns "failure", then write(a) will not be called. (I
don't recall what the value of `a` will be in that case.)

[https://en.wikipedia.org/wiki/Icon_programming_language#Goal...](https://en.wikipedia.org/wiki/Icon_programming_language#Goal-
directed_execution)

~~~
Evbn
That is like return codes in shell.

------
taylonr
This approach made me think of MFC calls. Instead of having multiple return
values you'd have a single return value and multiple out parameters.

It's been years since I've done any MFC work, but calls like:

HWND myWin = GetTopWindowTitle(region, out title, out size, out whateverElse);

Then you'd have to either assume that myWin was not an error code and just use
the out parameters, or you'd explicitly check the myWin value each time.

------
eldude
This is a lot like how node.js handles Errors and exceptions only its "panic"
call is throwing an Error either intentionally or via an uncaught exception,
which will crash the process and is difficult to catch due to node.js'
asynchronous nature. Apart from that, every function call returns both an
error and a value, and the value is irrelevant in the presence of an Error.

------
michaelt
I'm not familiar with Go.

If my code performs several operations in a row, any of which could return an
error, do I have to check the returned error value after each one?

For example if performing file or network I/O?

~~~
dscrd
Perhaps depending on how important it is for that piece of code to be
flawless, but generally yes.

The upside of all that is that you _can_ craft a piece of code to be
practically flawless, since you know how the libraries you are using are
supposed to break if they break.

------
seanalltogether
I would love to see some code to illustrate this article, as someone who
hasn't written anything in Go I still don't know what these mechanisms look
like in practice.

~~~
genwin
Here's some relevant code: <http://blog.golang.org/2010/08/defer-panic-and-
recover.html>

