Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

The lack of exceptions just seems like a total bear to me. I wrote C for 10 years and did tons of stuff like:

    Open file "foobar.dat". If that fails, abort with this error.
    Read the first 4 bytes into x.
      If that fails, abort with a different error.
    Seek to byte x. If that fails, abort with yet another error.
and so on, and so on, over and over again. Python's exceptions are a huge improvement over this pattern of timidly asking permission to do anything. The fact is there are so, so many occasions where you want to abort a computation if one of the many steps along the way goes wrong. Something like Haskell's Maybe monad is a good way of attacking the problem too.

But Go has neither. It seems to just offer the bad old clumsy C way and say, "Deal with it." To those who have written real Go programs, I'm honestly wondering: how is this not a pain in the ass?



Unlike C, Go isn't overloading what is returned, it has an extra param, and the language has been baked to handle doing like initialization and checks in single line if statements. It does force the developer who might actually have a clue what the exception is and how to fix it to handle it, but IMHO this is a good thing. Go forces lots of things like this (not using an import, won't compile; not using a variable, won't compile).

Honestly, compared to the exception hellscapes I have had to deal with in Java and C++ --- it seems like the path of least surprise. Which incidentally has been my favorite things about Go, the low number of surprises.

A lot of using Go in real work has gone against my expectations. There are a lot of things I initially saw as huge warts (lack of exceptions, generics and import versions), but I liked channels enough (Erlang background) to give it a shot. So far, I have been delighted by using it as a stack (build cycle, deploy method, terse C'ish syntax).


To make one correction, it isn't an "extra param", go has full support for multiple return values, full stop.


The problem with this "forcing" that go does is that it ALSO includes _, which means it's inevitable that lazy developers will get tired of handling error and just shunt it into _.


You can't stop people from being lazy. Look at all the Java and Python code that forcefully ignores exceptions.

It is something that can be caught with static analysis, however. Someone recently put together an appropriate tool[1] for Go, in fact. It seems to work very well.

[1]https://github.com/kisielk/errcheck


To be fair, the ability to use static analysis for error code checking is not something that is unique to Go. There was a paper recently on doing this for C (which found hundreds of bugs in the Linux kernel due to incorrect error code handling):

http://pages.cs.wisc.edu/~liblit/ghc-2011/ghc-2011.pdf

(Incidentally, the hard part of this analysis is not verifying that you checked the error, it's verifying that you propagated the error codes properly—that requires analyzing higher-order control flow.)


Go AST is something that I think will really help as it grows for writing great tooling.


To allow lazy error checking without losing safety, perhaps Go could special case _ so that error codes assigned to _ abort for failure values.


I would love something like this, actually.

I'm not sure I like the idea of special casing _ specifically, but a similarly concise way of saying "if this fails, this thread of execution is FUBAR" would be great.

"!!", maybe?


Could just be "!" by itself no?


They can't change that at this stage without breaking the Go 1.0 compatibility promise.


Unlike C, Go isn't overloading what is returned

In C, you can write code that doesn't overload errors and return values; e.g.,

err = someFunction(&returnValue, param1, param2, param3);

I'm not saying this is common -- and in fact the standard C library does a lot of the overloading you're talking about -- but for your own code and functions, you can separate out errors and return values, as shown above.


Writing exception-safe code is non-trivial. There has been a lot of debate recently about whether exceptions are a mis-feature. The main worry is that if you allow exceptions sensible-looking code can lead to many non-obvious bugs. This link gives some insight:

http://stackoverflow.com/questions/1853243/c-do-you-really-w...

Some more reasons why exceptions are problematic:

http://mortoray.com/2012/04/02/everything-wrong-with-excepti...

Go's choice I think is very well considered. If reliability is important to you then I believe handling errors explicitly leads to clearer, more correct code.


These criticisms of exception handling are primarily based on their implementation in C++; they are not issues inherent in exceptions. Exceptions are clearly more problematic when they are bolted-on after the fact to a language with manual memory management and a large existing body of code that is unaware of them.

But writing exception-safe code IS trivial, in basically any language except C++. Garbage collection is all you need in most cases, and try-with-resources/RAII/etc takes care of closing I/O.

Note that Go still needs defer() to reliably take care of the latter, so it's not clear to me what is gained by the omission (or rather, strong deprecation) of exceptions.


I'm writing a pretty big system in Go, and had not seen this as much of an issue. Could you provide more insight into your point? I'd like to see some code to compare, if its not to much to ask.


I'm not sure how great of an example it is, but I had the thought when recently rereading this routine from an old game, reading its level file:

        static void scanlevel(int num, FILE *fp)
    	{
    		filemap_t filemap;
    		int i;
    
    		if (fseek(fp, mapptrs[num-1], SEEK_SET))
    			error("Can't load level %d from blockman.lvl", num);
    
    		if (fread(&filemap, sizeof (filemap_t), 1, fp) < 1)
    			error("Can't load level %d from blockman.lvl", num);
    
    		if (filemap.startx < 0 || filemap.startx >= LEVWIDTH
    			|| filemap.starty < 0 || filemap.starty >= LEVHEIGHT)
    		{
    			error("Level %d is corrupt", num);
    		}
    
    		map.startx = filemap.startx;
    		map.starty = filemap.starty;
    
    		for (i = 0; i < LEVWIDTH*LEVHEIGHT; i++)
    		{
    			map.tiles[i] = (tiletype_t)filemap.tiles[i];
    			if (map.tiles[i] < 0 || map.tiles[i] >= NUMTILES)
    				error("Level %d is corrupt", num);
    		}
        }
This shows another benefit of exceptions, which is that if uncaught, they stop the program with a traceback of the exact point they occurred. So it's not even necessary to write most of the checks above. `error` here is a routine that aborts the program; you get that behavior by default with exceptions.

Whereas in C/Go if I forgot one of those error checks, the error would occur silently, leaving the program in some weird inconsistent state that I never planned for. It would just do something stupid and maybe crash or panic later on, far from the place where the initial error occurred.

I guess I'm just arguing for exceptions, which is old news as languages that have them have been around for quite a while. But Go doesn't offer much of a substitute of which I'm aware. The explanation of how it solves these problems has not been forthcoming.


I think the key difference is between assertions/"this should never happen" error checking, and actual error conditions that you want to pass back to client code, because it knows better than you do what the right thing to do is.

When you're writing reusable library code (and when you think about the scale of Google's codebase, they must have an insane amount of these libraries), it's important to make this distinction. There are some error conditions where you really just want to say "if this ever happens, just die, because there's nothing sane to be done", and Go provides panic() for these situations, similar to the error() function in your code above.

For situations where you do want to return a meaningful error to the client, I think Go's multiple return values provide a very good way to do it, far better than the overloading of NULL or -1 that you find in C and C++.


> I think Go's multiple return values provide a very good way to do it...

Better than C? Sure. But not better than languages that provide sum types; some of which have been around since the 70s.


The list of cool things Go doesn't have is a darn long one. They opted for simplicity at lots of points. I think it is worth noting these were decisions, not necessarily oversights.

There are a lot of great languages that end up mostly academic because they lack whatever the magical balance of features, simplicity and usefulness it takes for a language get mind share.

I suspect Go might have hit the magical balance with channels, strong types, great build system, simple minimal syntax and language keywords, fairly opinionated best practices (and formatting) and static single file deploys.


Multiple return values are something completely different from sum types. Just because Go by convention returns errors as an additional return values where other languages prefer sum types, you shouldn't conflate the two.


Parent didn't say they were the same, rather they implied that sum types are better.

Sum types can handle multiple return values seamlessly in a typesafe way as a special case, but are not limited to that because they may have different data shapes other than simple products, and callers can be checked to deal with each possible shape at each call site by the compiler.


Claiming sth is "better" requires two things to be comparable, and thus, reasonable similarity in their resp. nature. Claiming something is better than something else implies this similarity (otherwise any comparison would be moot), which I refuted in this particular case.

On a side note, the same people who claim that sum types are "better" are never able to come up with a constructive proposal how sum types could be integrated into Go in an elegant way.


So much hand-waving!

They are "better" in that they are, in fact, more constrained; only when the error case arises will there be any accessible error value; otherwise, the actual expected value will be found. Since go uses an ad hoc product type, you always get an error value and the return value, even if they are mutually exclusive most of the time.

Also, they are both ways to build larger types from smaller ones, and the way they go about doing it is rather obvious from their names, and thus the contrast.

> On a side note, the same people who claim that sum types are "better" are never able to come up with a constructive proposal how sum types could be integrated into Go in an elegant way.

Forgo the cutesy anonymous members for the massive benefits of sum types? For a team which prides itself for its ability to perform trade-offs, they sure were rigid in this stance.


> They are "better" in that they are, in fact, more constrained; only when the error case arises will there be any accessible error value; otherwise, the actual expected value will be found. Since go uses an ad hoc product type, you always get an error value and the return value, even if they are mutually exclusive most of the time.

But multiple return values are there for much more than returned result and error. You conflate that with the specific use of returning result and error, and based on that, you claim that sum types are better. That's a straw man par excellence.


Sum types and multiple return values are not mutually exclusive, though several languages today use tuples to emulate multiple return types (see Scala, Rust for examples).

I am not a language designer, but I have become interested in languages in the past couple of years. Sum types require some sort of generics implementation, which Go does not have. I think the design choices the authors made regarding the language have made adding generics that much harder, that now they are struggling to find the "Go way" of fitting them into the language.


This is the most illuminating reply in the subthread for me. Very good point, thanks for the clear explanation.


Does panic isolate gracefully a query-of-death, or does it take the entire server down?

Does panic provide the stack-trace?


A panic unrolls the stack until it's recovered from. If it's not recovered the entire server will go down.

If the panic isn't recovered it will print a stack trace, if it is recovered you can get a stack trace with runtime.Caller().

Go's panics are exceptions.


Thanks!



The explanation of how it solves these problems has not been forthcoming.

It'd be interesting to see what you'd do with the code above in C++, and where you'd put the error handling code for diverse errors that might occur reading this particular file.

The Go approach is to handle errors locally, often in the calling method, which makes it clear where they are handled and what the outcome is, and easier to recover gracefully, without unexpected exceptions from code in libraries or other code in the program. Some large users of C++ (like Google) refuse to use C++ exceptions in their own code - so they are not entirely without controversy.

In the code above, if you used exceptions, and relied on the libraries to throw exceptions for errors, you'd have to throw your own exception at:

error("Level %d is corrupt", num);

So you'd have a mix places where exceptions were generated (in unknown lib code, in your code) and an unknown (for the reader) mix of places where they are handled. I'm sure this could be done gracefully, but it does mean errors missed might be handled at a much higher level in the code, far away from where they were generated, which can lead to errors being missed until it is too late to do anything but output a stack trace and exit, which to the user seems equally stupid as crashing or panicking at some later point.

If you exit the program on simple errors like being unable to load a single game file, it's not very pleasant for the user - I'd expect it instead to recover gracefully and show the user an error before continuing, which is easy enough when using Go's pattern of error returns, and harder with exceptions where you have unrolled the stack possibly past the loading code, unless you start handling exceptions in calling code one level up, which looks very much like the error handling of Go. So there are trade-offs to using either method aren't there?


Not using C++ exceptions in Google's code bases is partly due to historic reasons (since the original code base did not have it), and partly due to them being "harder" to implement in C++ correctly.

They even acknowledge in their C++ style guide[1] that: " Things would probably be different if we had to do it all over again from scratch."

[1] http://google-styleguide.googlecode.com/svn/trunk/cppguide.x...


I'm not clear on which error check you think you might "forget". If you're performing an operation that can fail, wouldn't that be a clue that you need to check for failure?

You can't actually write code like that in Go, by the way. It's not going to let you read data directly into a struct like that, nor should you really be doing so in the first place.

I'd be happy to provide a more detailed analysis, and possibly even Go-equivalent code, but without further context (at least the definitions for `filemap_t` and whatever struct type `map` is, if not a full description of the file format and its meaning), it's impractical.


> I'm not clear on which error check you think you might "forget". If you're performing an operation that can fail, wouldn't that be a clue that you need to check for failure?

Sure - and the possibility of exceeding the bounds of an array would be a clue that you need to bounds check, but there's still a hell of a lot of C code out there with array overflow errors. You can argue that people who make these errors are bad programmers, but that's fairly irrelevant - most programmers of any level will end up working with code with errors in it at some point. Exception stack traces are an extremely useful way to find out where something went wrong when someone failed to do some necessary error checking.

I have no experience with Go, so I'm not saying what it does is wrong - I'm just curious. Say a customer experiences a failure with your software caused by some missing/incorrect error handling, what do you do to work out what happened?


> the possibility of exceeding the bounds of an array

Not possible in Go, the runtime will panic.

> there's still a hell of a lot of C code out there with array overflow errors

An extremely easy error to make in many cases, which is why modern programming languages bounds-check.

> Exception stack traces

Go will give you a very nice stacktrace should it ever panic.

> I have no experience with Go

Which is really the problem. People keep arguing about Go's merits based on no substantive understanding.

It's very obvious which operations can fail without a panic in Go, because functions explicitly return error objects -- actual error objects, not magic numbers. The return signature for the Go equivalent of fread is (int, error), not (int).


> Which is really the problem. People keep arguing about Go's merits based on no substantive understanding.

They are not arguing, they are asking questions to improve their understanding.


That may have been too harsh in reply to AlisdairO specifically, in which case I apologize.

The context of the thread, however, very much is people arguing. See for example graue's post a few levels up.


While I may have phrased my post somewhat harshly, I too asked because I wished to improve my understanding, and the responses have definitely helped.


> > the possibility of exceeding the bounds of an array > Not possible in Go, the runtime will panic.

Is -B for disabling bounds checking no longer supported?


I see no such option documented, and I would certainly never enable such a thing in real code.


It was never documented, but any long time Go user knows about it.

Looking at the code it seems it has been removed.

> ... I would certainly never enable such a thing in real code.

So I assume you don't do C or C++. :)

While I agree with you, there are certain cases where it might help. That is why most strong typed languages with native compilers allow to selectively disable bounds checking, since the Pascal/Modula-2 days.

I only support doing this if profiling proves it is really worth it, give the security issues.


I think it's still there.

    package main

    func main() {
        slice := []string{"first", "second", "third"}
        println(slice[1])
        slice = slice[0:2]
        println(slice[2])
    }
Compile with

    go build -gcflags -B wat.go
    ./wat
Output

    second
    third
Without the `-B` you'll get a runtime panic. I'm on Go 1.1. (Maybe it's removed in tip?)


Ah ok, I was trying to locate it via the browser.

Next time I better checkout and do a proper grep.


> So I assume you don't do C or C++. :)

If C had a simple universal switch for bounds checking, I'd turn it on everywhere and immediately revoke commit privileges for anyone on my team who turned it back off. But it doesn't, and necessarily can't, making your statement nothing more than an annoying exercise in wrongful pedantry. It is contextually obvious I was talking about Go code and/or languages/compilers with such a switch.


I was trying you out, because if you make such a statement then I am lead to believe you stay away from languages that don't provide control over bounds checking.


See the output of

    go tool 6g -help
The `-B` switch will disable bounds checks. You can use it like this:

    go build -gcflags '-B'




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: