I totally agree; I've "gone off Go" until they figure out (1) errors vs exceptions, and (2) generics. I have no problem with them saying "we're not Going there", but if so I wish they would at least say that. Right now, I'm betting at least one of those positions will change, and it will have fairly big ramifications for any existing code (in the same way that Java 1.0 code still runs, but is rather different code).
I don't think they're "figuring out" exceptions; my understanding is, they're not going to happen.
I am also not a fan of hyperliteral case-by-case error checking (it reminds me of my early C code), but it's A Style, one that the Golang team enthusiastically adopts, and it's unlikely to go anywhere.
More than just the normally used: if err := foo(); err != nil; { return err }
Large code Go code basis are so much better (more maintainable) for "hyperliteral case-by-case error checking". My impression that only people who havn't written large Go projects have this complaint.
Also, there is no reason you could not define a function like:
That seems to me like an awful example. But speaking of generics, exceptions and other things that Go lacks, like pattern matching, lazy values, currying and so on, in my opinion Go sucks because you can't abstract well over common patterns. To take your example as a use-case:
object NonFatalError {
def unapply(err: Throwable) = err match {
case _: TimeoutException => Some(err)
case _: IOException => Some(err)
case _ => None
}
}
def executeWithRetries[T](maxTries: Int)(callback: => T): T =
try {
callback
}
catch {
case NonFatalError(error) if maxTries > 0 =>
logger.warn(error)
executeWithRetries(maxTries - 1)(callback)
case other: Throwable =>
throw other
}
And usage:
def funcMayErr(): Int = throw new TimeoutException
val value = executeWithRetries(5) {
funcMayErr()
}
But there's more. Because with generics and exceptions you can actually wrap the whole result in an algebraic data-type that also has handy methods for dealing with failure (e.g. a monad), as in:
Try(executeWithRetries(5)(funcMayErr)).map(x => x + 1).getOrElse(default)
* those casually glancing over to figure what the code does
* those actually trying to figure out what a given expression does, either because they are reviewing or debugging
* compilers are people too
Conciseness is often overvalued and pursued to the extreme where effort is made first by the author to seek for the perfect oneliner, and then for the reader to actually check that this code is doing what expected.
Composition is important, but I don't think I found great real world examples of composition which wasn't either working only because of a tightly controlled code base or because it was just an example to prove a point.
Don't get me wrong, I love scala/haskell, I find playing with those constructs interesting and beautiful.
It's just that Go is a different thing, is a modern approach of getting back to basics, a minimal toolset for do just programming, more or less translation of thought into instructions.
And it's works very well; it's very easy to get things done quickly and the produced code tends to be easily maintainable. It's easy to have control over the memory footprint. The tooling is very mature (http://blog.golang.org/race-detector, gofmt formatting+refactoring)
I think many of us want Go to be something it doesn't want to and will never be. There is potential for a fast, simple, statically typed language; borrowing good ideas from Lisp, ML and others (and NOT resulting in something like Scala).
I'm not sure how that code example demonstrates something other than error checking that has to be written case by case at the call site of any function that might return an error.
Well it does demonstrate that, and I think that is a good attribute of Go. (And if the caller returned the error, it could be handled by a previous caller too btw)
Not if you want to parameterize the exceptions. This kind of "matching" requires that you compare your exception "types" by exception "values", so you cannot provide parameterized information about what exactly failed.... which key? Which URL?
Underpowered error handling (and I'm not advocating for exceptions, persay), lack of generics (and the resultant copypasta party and interface{} runtime casting [aka, the return of void *]) are real warts in an otherwise fine language.
And I'm not just theorizing: I spend my days writing a large, nontrivial system in go.
I've used Haskell a lot before, and I'm not asking for no nulls or real ADTs (though I wouldn't complain), but generics + better typed errors would really help clean things up.
Meanwhile, a lot of us are just waiting for rust...
It just depends on what you're trying to accomplish, but most use-cases can be accomplished without exceptions. The other use-cases often indicate bad design.
As for generics, you can get 90% of the way there with interfaces. The use of `interface{}`--while sometimes necessary--is often an indicator of bad design.
In large code bases, you often don't need (or care) to know what underlying type something is. For example, you shouldn't care whether an `io.Reader` is a TCP socket, file or completely in-memory ala `io.Pipe()`.
There are times when type assertions are the best/only way to get something done, and that's why they're there, but those cases should be relatively infrequent.
Generics would make some things easier (Rust's implementation is quite nice), but it's not significantly impacting my productivity, and I certainly wouldn't consider switching languages just because Go lacks them.
Of course you can, error is an interface, any number of concrete types can satisfy it, and you can use type assertions or type switches to unbox (and use) that concrete type.
Exceptions are already in the language: panic/recover. Though discouraged, you can't assume they won't happen. To me, this seems like the "worst of both worlds". It doesn't feel like a final position to me, it feels like a 1.0 position.
But panics are not exceptions. They should not be used like exceptions, and there is no "hierarchy of panic types".
They are used for things like out of bounds indexes, where in C it would simply be a segfault. A panic is a way of gracefully exiting a program that would have segfaulted otherwise. Correct code should check for out of bound indexes either way.
I agree, but would also point out that panic isn't necessarily going to exit a program, recover exists and it isn't that uncommon for programs or libraries to do a deferred recover at the beginning of goroutines so that a panic within that goroutine will only kill off that goroutine and allow the main goroutine and other goroutines to continue.
Of course this "pattern" should only be used when you're sure that that one failing goroutine won't have a cascading impact on other goroutines that are still running.
I think I've answered this elsewhere: the issue for me is that you have to handle exceptions _and_ error codes.
Though you raise an interesting point: Should you check array indexes if the runtime is also checking it for you?
In Java, the runtime is guaranteed to throw an exception and it is relatively rare that you would pre-check the array indexes (you might use assertions in debug code).
Incidentally, array bounds checking is actually relatively expensive, to the point where most JVMs (which use signed indexes) use an unsigned comparison trick to make it one comparison instead of two. So it does matter...
the issue for me is that you have to handle exceptions _and_ error codes.
Except you don't. I haven't used recover in any of my code for a long time (more than a year). Most of the time you don't need to worry about handling panics, but you can if you really need to.
Should you check array indexes if the runtime is also checking it for you?
In Go the generated code does it. You shouldn't do it yourself.
I consider defer to be part of handling exceptions, but I can see how we differ here.
We're seriously drifting off track here, but if we should rely on Go to check array indexes, that seems like you _would_ want a recover block, so that we can map it to a Go-preferred error code?
Go doesn't have exceptions. Can you please stop saying it does? There's a reason we didn't give "panic" the name "throw". Because they work differently and are used for different things.
There's also no such thing as a "recover block" (you're thinking of a "finally block" or "catch block", neither of which exist in Go).
If we thought you should use recover any time there might be an array out of bounds panic, we'd have designed the whole language differently. Panics should happen when things go badly wrong, and most of the time that means your program should crash.
You should use recover only in two rare cases: 1. where you're specifically using panic/recover as a kind of setjmp/longjmp (as it is used within encoding/json, for example), and 2. where you don't want a programming error to bring down your entire program, such as in the base net/http handler (although I think it's debatable whether we should have done it there; but it's done now and we can't change it).
It amazes me that there has been so much discussion over this incredibly minor and seldom-used feature. Just return and check errors (and just panic when things go really wrong) and get on with your life.
Doesn't the http package's Server recover from panics in the goroutines that are created to serve requests? That bugged me when I saw it happen. If my request handler panics, I wouldn't expect the server to recover from it.
You can abuse "panic" to implement exceptions in Golang the way you can abuse "longjmp" to do that in C. The purpose of "panic" isn't for general-purpose exceptions. It's to panic the program.
But correct code must assume that any function you call might throw; which is why you should use defer blocks e.g. to release resources, instead of C-style "cleanup at the bottom of the function". (Defer is also prettier IMHO)
It's idiomatically correct to ignore panics and let them take the whole program down with a crash. A panic indicates that your program is already doing extremely incorrect things. Rare is the program where picking itself up and continuing a possible Sorcerer's Apprentice mode rampage is better than just stopping and telling you what needs fixing.
Or raising?
That's just terminology. The behaviors of c++/java/python exceptions and Go panics are similar regardless of the name the keyword has in those language:
The execution is suspended, the stack unwound until the first handler, the handler has access to a value that is "thrown". Stack information is preserved in order to print meaningful stack traces.
C++/java/python have syntax sugar that performs a pattern match on the thrown object to decide whether to handle it or bubble it up, while in Go you do it manually, but other that that I don't see much of a difference in the mechanics of them to justify being so pedantic about the naming of the action.
The point is that an exception in Go world means: something which should not ever happen, and which renders continued execution impossible. IMO recover should only be used to wind down execution in as graceful a manner a possible prior to terminating the program.
As opposed to an error, which can and will happen.
Here's what's going to happen: neither parametric polymorphism, nor exceptions are going to happen in Go. The answer in the FAQ is just PR bullshit; the designers of Go are not interested in having parametric polymorphism and that's the bottom line.
You can say that until you're blue in the face, but the fact is we continue to discuss generics to this day and are very much interested in including them in the language.
Why do people keep ignoring me when I say this? I guess the idea that the Go team are a bunch of generics-hating curmudgeons is more compelling than the reality.
Perhaps you should blog about "Generics in Go discussions"? Then you would have some proof and dispel mistruths about Go. I would also get excited, since reality seems like Go developers don't care that much about generics.
However, I'm taking into account you said that they do :)
As I understand it, the position is that there are error return codes _and_ exceptions. Error return codes are for bad stuff, Exceptions are for really bad stuff. So correct Go code pays the price twice: it should be exception-safe, _and_ you have to manually handle error codes.
Golang does not have exceptions. It has "panic", which, you can see from the name, is meant to end a program's execution and is presumably "recoverable" only so that programs can wind themselves down (more) gracefully.
My concern is not nomenclature, but that correct code must handle paniceptions. And that it must also handle return value errors. Go code IMHO ends up spending a lot of code on error handling, I think because of this double taxation.
I am writing a server which will maintain thousands of SSL-encrypted simultaneous connections for a real-time application. If ONE panic makes its way to the top of the relevant goroutine, the whole process comes down, trashing all my connections in the process. It is non-trivial to reestablish them. Yes, my system is built to handle this case, but it's still not something I can afford to have happen every 15 seconds due to some error that is only affecting one out of my thousands of connections. (Also, I do understand why this is the only choice the Go runtime can make; this is not a complaint.)
At least in my world, every time I type "go", I must ensure that I am starting a goroutine that has some sensible top-level recover mechanism, and, honestly, for any Go program that actually plans on using concurrency, I think there's no alternative. You MUST handle panics. Why? Because an important aspect of Go's concurrency is maintaining composition of independent processes, and there are few things more uncompositional as a completely unrelated computation in a completely unrelated thread that trashes your entire OS process.
Panics may be for programming mistakes, but for any non-trivial code, you have some. Hopefully you can work out a better way of handling them than completely bringing the entire program down.
I am willing to assert that my code maintains enough isolation that continuing on is a reasonable thing to do. (It's a port of Erlang code anyhow. This is not a very freaky claim about such code.)
Note I said "as a matter of course". I agree it's useful in certain very limited circumstances, like parsing. But certainly not database work, unless you have a very different idea of what that entails than I do. Link to code?
It's the same principle as parsing. Database work involves lots of querying, scanning, etc. All of these operations produce errors. In the work that I do, the response to an error is usually, "rollback, show error to user." This makes it ideal for panic/recover. (And this can work well for either command line applications or web applications.)
Panicking isn't done when a database operation fails. Returning an error value is. It's just like old-school C. Panics are for programming errors or things like out of memory conditions, not errors in ordinary operation, even when components are failing.
I have exactly two panics in my ~15k line server. Both are in initialization code that will probably never get called, so it will fail very early on in the code. The rest of my code looks like this:
func getRecord(args...) (err error) {
if err := doSomethingRisky(); err != nil {
return err
}
if err := doSomethingElseRisky(); err != nil {
return err
}
... other code ...
return nil
}
func processRecord() error {
if err := getRecord(args...); err != nil {
return err
}
... do other stuff ...
return nil
}
All the way down the stack. It's certainly a little more code, but it forces you to at least acknowledge all errors. If you want a stack-trace, you can always use the runtime package.
I hate to be rude, but I feel like you jumped into this thread without reading the context.
I'm not talking about panicing instead of returning errors. I'm talking about using an idiom---which is used in the Go standard library (see my link up-thread)---to make error handling more terse when you're working with code that is otherwise profligate with checking errors.
At no point is a panic exposed to the user of a program or to the client of a library. At no point are errors ignored. The panics are kept within package and converted to error values.
Guarding your library boundary with a recover doesn't absolve your library internals from being nonidiomatic by using panics. (That the stdlib uses panic/recover in a few specific places does not make it broadly idiomatic.)
Without seeing specific code I can't say for sure, but it's very unlikely that any database interaction code is best modeled with panic/recover for error handling. I'm very curious to see the source, at this point.
Guarding your library boundary with a recover doesn't absolve your library internals from being nonidiomatic by using panics.
Using panic/recover doesn't automatically make your code nonidiomatic.
(That the stdlib uses panic/recover in a few specific places does not make it broadly idiomatic.)
That the stdlib uses panic/recover in several places is a good indicator that "never use panic/recover" is bad advice. Note that while I agree that just because something is in the stdlib doesn't mean it's idiomatic, I also cite that this particular approach is used to make the structure and organization of code more clear. Since it's used in several packages, I claim that this is a strong hint that panic/recover is appropriate in limited scenarios.
Without seeing specific code I can't say for sure, but it's very unlikely that any database interaction code is best modeled with panic/recover for error handling. I'm very curious to see the source, at this point.
We seem to have some wires crossed. Let's be clear, shall we?
* The panic/recover idiom is rarely used, but it is an idiom.
* There are trade-offs involved with using panic/recover. In my sample linked in this comment, many of the functions in database/sql need to be stubbed out so that they panic. However, the cost of this is relatively small, since it can mostly be isolated in a package.
* The idiom is most frequently seen in parsing because there are a lot of error cases to handle and the response to each error is typically the same.
* While parsing is the common scenario, I claim it is not the only one. I cite that database work is profligate with error checking, and depending on your application, there's a reasonable chance that the response to each error is going to be the same. When doing a lot of it, it can pay off to use the panic/recover idiom with similar benefits as for doing it with parsing.
* There may well be other scenarios where such handling is appropriate, but I have not come across them.
I've done an unusual amount of work with parsers and have done some database work, so I've had the opportunity to bump up against the panic/recover idiom a bit more than normal. As with anything else, it can be abused. But I find it extraordinarily useful in certain situations.
This is just another example of Go's fundamental attitude. Stuff is available for the language designers, but not for you :
* generic functions (e.g. append)
* generic data types (e.g. slices)
* exceptions (like illustrated above)
* special case syntax
* Custom event loops
* precompiler macros (very bad to use, horrible, blah blah ... except of course for the people imposing this restriction, and YES they're using it amongst other things to workaround the lack of generics in C)
...
This attitude was common in middle-90s "generic" programming languages like Ocaml, Modula-2 and others. You should simply look at Go as one of those languages and treat it as such.
If this attitude bothers you, you should look at C++0x and D.
I'm confused as to what you guys are saying: are you saying that you don't need to handle exceptions (whether using defer or recover), or that it's better to use defer over recover? I take exception to the former, totally agree with the latter.
defer and recover have nothing to do with each other, except that in the few circumstances where it's appropriate to use recover, you often do it within a defer block.
> are you saying that you don't need to handle exceptions
> (whether using defer or recover)
Go doesn't have exceptions. You don't need to handle (i.e. explicitly deal with) panics via recover. If you do, especially if you're not making the panics yourself in e.g. a parsing package, that's a bad code smell and you're probably doing something wrong.
My understanding is that defer is the broad equivalent of a Java finally block, and recover is the broad equivalent of a Java catch block. I think of both as ways of handling exceptions, although I see how the word "handle" could be interpreted in a way that makes my statements nonsensical. By handling I meant "doing the right thing", not "swallowing the panic/exception"; I apologize for the ambiguity you found.
If you do still think I have gaps in my knowledge, I humbly suggest that you briefly fill in those gaps with facts; it should save you time in the long run and will likely win you a few converts!
You're looking at it wrong. Stop thinking about Java. Go programmers rarely use recover. There are many that have probably never used it at all. Go does not have exceptions. Panics are only vaguely like exceptions, but you shouldn't even think about them in those terms. It is confusing you badly.
Defer is primarily used to make sure that clean-up happens in functions that have multiple exit points (return statements). It's a convenient side effect that defers are executed while a panic unwinds the stack, but it is rarely the first thing on the mind of the Go programmer when they type "defer".
Embrace error values. Return them! Check them! Panic when shit goes really bad. That's it. If you're writing Go code and you're thinking about "throw" "catch" or "finally", you're doing it wrong. Go's features do not map cleanly to those concepts, because Go doesn't have exceptions.
I think you're misunderstanding justinsb's point a little bit. To be concrete, you have to remember to use "defer" in Go to clean up resources and locks, or else someone trying to use "recover" won't handle panics properly.
This won't unlock the mutex on panic, which is observable if someone is trying to recover():
func F() {
mutex.Lock()
... do something here that panics ...
mutex.Unlock()
}
But this will:
func F() {
mutex.Lock()
defer mutex.Unlock()
... do something here that panics ...
}
This is basically the same set of hazards as maintaining exception-safety in C++ or Java. So in this regard panic is very much like an exception system. (Of course, it has very different idiomatic use.)
... To be concrete, you have to remember to use "defer" in Go to clean up resources and locks, ...
You should probably be using defer() all the time anyway, unless you have a good reason not to. It also helps with code evolution, in the cases where some yahoo adds a new return statement in the middle of a function.
Best practices for try/catch may be similar to idiomatic defer, but it's not the same semantically. For example, there's no analog to this:
try {
... do something that throws ...
} catch (...) {
... deferred code
}
... other code
If you don't use `catch`, then I could agree that try/finally is the same as defer, but I would argue that defer is a much cleaner design since cleanup code is located next to the thing they're cleaning up.
Other languages also make a distinction here, for example Python's `with` and D's `scope` [1].
Also, it's trivial to make a `panic` that is unrecoverable: `go panic("broke your code, lol!!")`. This just cements the idea that `panic` is semantically different than exceptions, and should be treated as such.
Best practices for try/catch may be similar to idiomatic defer, but it's not the same semantically. For example, there's no analog to this:
You can do that by creating another function.
Also, it's trivial to make a `panic` that is unrecoverable: `go panic("broke your code, lol!!")`. This just cements the idea that `panic` is semantically different than exceptions.
That's not different. In, say, Java, you can set the default uncaught exception handler to get the same behavior and then you can write:
new Thread() { throw new RuntimeException("..."); }
You can't emulate the behavior of continuing the current block after an exception is caught. You have to recover() and copy/extract into a function any code that you'd want to run in the recover.
For example:
try {
... code that throws
} catch {
}
... other code
In Go, to run "other code", you'd have to duplicate all of that logic in the recover():
defer func() {
if err := recover(); err != nil {
... other code (duplicated from below)
}
}()
... code that panics
... other code
This isn't really the same thing, but I suppose you could technically get the same effect if you move all of "other code" into a function and called that in both places, but you're still duplicating code.
Panics and exceptions are two very different things, which is why there are different idioms in place to make working with them safe.
This isn't really the same thing, but I suppose you could technically get the same effect if you move all of "other code" into a function and called that in both places, but you're still duplicating code.
Right. It's a pretty trivial transformation, and that's why it's not inaccurate to call panic/recover equivalent to exceptions: you can straightforwardly express every exception-based pattern using defer/panic/recover, and also the other way round. Sometimes you have to make more functions to make panic/recover work, but that's part of the "tied in with function declarations" nature of panic/recover/defer—there's nothing semantically that deep about it because the transformation is still quite simple.
I suppose that's technically true, so I'll have to concede the point. However, I still maintain that it's not practically true, since it has a very different flow than exceptions.
Anyway, I assume you're the same pcwalton from Rust? I really like the design of error handling so far, especially the bit about trapping conditions. From what I read, it looks failure just kills the task, instead of the entire program (like it does in go if unhandled).
I'm really looking forward to 1.0. Keep up the good work!
Comments
I totally agree; I've "gone off Go" until they figure out (1) errors vs exceptions, and (2) generics. I have no problem with them saying "we're not Going there", but if so I wish they would at least say that. Right now, I'm betting at least one of those positions will change, and it will have fairly big ramifications for any existing code (in the same way that Java 1.0 code still runs, but is rather different code).
I don't think they're "figuring out" exceptions; my understanding is, they're not going to happen.
I am also not a fan of hyperliteral case-by-case error checking (it reminds me of my early C code), but it's A Style, one that the Golang team enthusiastically adopts, and it's unlikely to go anywhere.
Go's returned error allows very varied error handling if wanted/needed: http://play.golang.org/p/-2q6N08x_P
More than just the normally used: if err := foo(); err != nil; { return err }
Large code Go code basis are so much better (more maintainable) for "hyperliteral case-by-case error checking". My impression that only people who havn't written large Go projects have this complaint.
Also, there is no reason you could not define a function like:
That seems to me like an awful example. But speaking of generics, exceptions and other things that Go lacks, like pattern matching, lazy values, currying and so on, in my opinion Go sucks because you can't abstract well over common patterns. To take your example as a use-case:
And usage: But there's more. Because with generics and exceptions you can actually wrap the whole result in an algebraic data-type that also has handy methods for dealing with failure (e.g. a monad), as in: Cheers,There are 3 kinds of people that read code:
* those casually glancing over to figure what the code does * those actually trying to figure out what a given expression does, either because they are reviewing or debugging * compilers are people too
Conciseness is often overvalued and pursued to the extreme where effort is made first by the author to seek for the perfect oneliner, and then for the reader to actually check that this code is doing what expected.
Composition is important, but I don't think I found great real world examples of composition which wasn't either working only because of a tightly controlled code base or because it was just an example to prove a point.
Don't get me wrong, I love scala/haskell, I find playing with those constructs interesting and beautiful.
It's just that Go is a different thing, is a modern approach of getting back to basics, a minimal toolset for do just programming, more or less translation of thought into instructions.
And it's works very well; it's very easy to get things done quickly and the produced code tends to be easily maintainable. It's easy to have control over the memory footprint. The tooling is very mature (http://blog.golang.org/race-detector, gofmt formatting+refactoring)
I think many of us want Go to be something it doesn't want to and will never be. There is potential for a fast, simple, statically typed language; borrowing good ideas from Lisp, ML and others (and NOT resulting in something like Scala).
You just don't know Go if you think it "can't abstract well over common patterns".
Well it can't. Take for example "sort", "map", "filter" etc.
That it can abstract some other "common patterns" doesn't solve this.
func execWithRetries(f func() error, retryc int) error
can easily be implemented in Go: http://play.golang.org/p/kMNqfY7LYX
I'm not sure how that code example demonstrates something other than error checking that has to be written case by case at the call site of any function that might return an error.
Well it does demonstrate that, and I think that is a good attribute of Go. (And if the caller returned the error, it could be handled by a previous caller too btw)
What I was showing was that:
Type logic is completely possible, which while apparent to you is lost on some people who haven't work with non exception langs before.Not if you want to parameterize the exceptions. This kind of "matching" requires that you compare your exception "types" by exception "values", so you cannot provide parameterized information about what exactly failed.... which key? Which URL?
Underpowered error handling (and I'm not advocating for exceptions, persay), lack of generics (and the resultant copypasta party and interface{} runtime casting [aka, the return of void *]) are real warts in an otherwise fine language.
And I'm not just theorizing: I spend my days writing a large, nontrivial system in go.
I've used Haskell a lot before, and I'm not asking for no nulls or real ADTs (though I wouldn't complain), but generics + better typed errors would really help clean things up.
Meanwhile, a lot of us are just waiting for rust...
Maybe I'm missing something, but you can do this with a type switch: http://play.golang.org/p/jF_bPQdwxk
It just depends on what you're trying to accomplish, but most use-cases can be accomplished without exceptions. The other use-cases often indicate bad design.
As for generics, you can get 90% of the way there with interfaces. The use of `interface{}`--while sometimes necessary--is often an indicator of bad design.
In large code bases, you often don't need (or care) to know what underlying type something is. For example, you shouldn't care whether an `io.Reader` is a TCP socket, file or completely in-memory ala `io.Pipe()`.
There are times when type assertions are the best/only way to get something done, and that's why they're there, but those cases should be relatively infrequent.
Generics would make some things easier (Rust's implementation is quite nice), but it's not significantly impacting my productivity, and I certainly wouldn't consider switching languages just because Go lacks them.
EDIT: Added info about generics
Errors in Go are custom types; your error can be defined as, for example:
At which point a caller that wants to handle this error can either extract the URL to do fun things with it or just dump the Error() string.Of course you can, error is an interface, any number of concrete types can satisfy it, and you can use type assertions or type switches to unbox (and use) that concrete type.
This is a widely used idiom in Go.
Exceptions are already in the language: panic/recover. Though discouraged, you can't assume they won't happen. To me, this seems like the "worst of both worlds". It doesn't feel like a final position to me, it feels like a 1.0 position.
But panics are not exceptions. They should not be used like exceptions, and there is no "hierarchy of panic types".
They are used for things like out of bounds indexes, where in C it would simply be a segfault. A panic is a way of gracefully exiting a program that would have segfaulted otherwise. Correct code should check for out of bound indexes either way.
I agree, but would also point out that panic isn't necessarily going to exit a program, recover exists and it isn't that uncommon for programs or libraries to do a deferred recover at the beginning of goroutines so that a panic within that goroutine will only kill off that goroutine and allow the main goroutine and other goroutines to continue.
Of course this "pattern" should only be used when you're sure that that one failing goroutine won't have a cascading impact on other goroutines that are still running.
Not all exception systems have type hierarchies. Neither ML nor Haskell have inheritance and both have exceptions.
Haskell exceptions form a hierachy though.
I think I've answered this elsewhere: the issue for me is that you have to handle exceptions _and_ error codes.
Though you raise an interesting point: Should you check array indexes if the runtime is also checking it for you?
In Java, the runtime is guaranteed to throw an exception and it is relatively rare that you would pre-check the array indexes (you might use assertions in debug code).
Incidentally, array bounds checking is actually relatively expensive, to the point where most JVMs (which use signed indexes) use an unsigned comparison trick to make it one comparison instead of two. So it does matter...
If you're recovering from panics, in general, you're doing something wrong.
Except you don't. I haven't used recover in any of my code for a long time (more than a year). Most of the time you don't need to worry about handling panics, but you can if you really need to.
In Go the generated code does it. You shouldn't do it yourself.
I consider defer to be part of handling exceptions, but I can see how we differ here.
We're seriously drifting off track here, but if we should rely on Go to check array indexes, that seems like you _would_ want a recover block, so that we can map it to a Go-preferred error code?
Go doesn't have exceptions. Can you please stop saying it does? There's a reason we didn't give "panic" the name "throw". Because they work differently and are used for different things.
There's also no such thing as a "recover block" (you're thinking of a "finally block" or "catch block", neither of which exist in Go).
If we thought you should use recover any time there might be an array out of bounds panic, we'd have designed the whole language differently. Panics should happen when things go badly wrong, and most of the time that means your program should crash.
You should use recover only in two rare cases: 1. where you're specifically using panic/recover as a kind of setjmp/longjmp (as it is used within encoding/json, for example), and 2. where you don't want a programming error to bring down your entire program, such as in the base net/http handler (although I think it's debatable whether we should have done it there; but it's done now and we can't change it).
It amazes me that there has been so much discussion over this incredibly minor and seldom-used feature. Just return and check errors (and just panic when things go really wrong) and get on with your life.
Doesn't the http package's Server recover from panics in the goroutines that are created to serve requests? That bugged me when I saw it happen. If my request handler panics, I wouldn't expect the server to recover from it.
You can abuse "panic" to implement exceptions in Golang the way you can abuse "longjmp" to do that in C. The purpose of "panic" isn't for general-purpose exceptions. It's to panic the program.
Note that this mechanic is used in the golang standard library and works great as a catchall: http://golang.org/src/pkg/text/template/exec.go#L93
It's clear you shouldn't throw.
But correct code must assume that any function you call might throw; which is why you should use defer blocks e.g. to release resources, instead of C-style "cleanup at the bottom of the function". (Defer is also prettier IMHO)
It's idiomatically correct to ignore panics and let them take the whole program down with a crash. A panic indicates that your program is already doing extremely incorrect things. Rare is the program where picking itself up and continuing a possible Sorcerer's Apprentice mode rampage is better than just stopping and telling you what needs fixing.
You're not "throwing". You're "panicking".
Or raising? That's just terminology. The behaviors of c++/java/python exceptions and Go panics are similar regardless of the name the keyword has in those language:
The execution is suspended, the stack unwound until the first handler, the handler has access to a value that is "thrown". Stack information is preserved in order to print meaningful stack traces.
C++/java/python have syntax sugar that performs a pattern match on the thrown object to decide whether to handle it or bubble it up, while in Go you do it manually, but other that that I don't see much of a difference in the mechanics of them to justify being so pedantic about the naming of the action.
The point is that an exception in Go world means: something which should not ever happen, and which renders continued execution impossible. IMO recover should only be used to wind down execution in as graceful a manner a possible prior to terminating the program.
As opposed to an error, which can and will happen.
we should rename recover to "log this before terminating the process"
Here's what's going to happen: neither parametric polymorphism, nor exceptions are going to happen in Go. The answer in the FAQ is just PR bullshit; the designers of Go are not interested in having parametric polymorphism and that's the bottom line.
FWIW, Limbo, Go's ancestor from the same author, does have generics.
You can say that until you're blue in the face, but the fact is we continue to discuss generics to this day and are very much interested in including them in the language.
Why do people keep ignoring me when I say this? I guess the idea that the Go team are a bunch of generics-hating curmudgeons is more compelling than the reality.
Probably because a lot of Go users _are_ generics-hating curmudgeons :-) (even though the team aren't)
Perhaps you should blog about "Generics in Go discussions"? Then you would have some proof and dispel mistruths about Go. I would also get excited, since reality seems like Go developers don't care that much about generics.
However, I'm taking into account you said that they do :)
Such a blog post would be a lot of effort and detract from the many other important things we have going on.
Then you should stop complaining that people consider you liars. :-)
Understandable. Only you can decide whether focusing on those other things or a blog post like that is more important.
I don't blame you for not writing it up, I don't think I would either ;)
What is there to figure out about errors vs exceptions? I think the Go position on that matter is very clear.
As I understand it, the position is that there are error return codes _and_ exceptions. Error return codes are for bad stuff, Exceptions are for really bad stuff. So correct Go code pays the price twice: it should be exception-safe, _and_ you have to manually handle error codes.
Golang does not have exceptions. It has "panic", which, you can see from the name, is meant to end a program's execution and is presumably "recoverable" only so that programs can wind themselves down (more) gracefully.
You say panic, I say exception :-)
My concern is not nomenclature, but that correct code must handle paniceptions. And that it must also handle return value errors. Go code IMHO ends up spending a lot of code on error handling, I think because of this double taxation.
No, correct code doesn't have to handle panics. Panics are for programming mistakes.
I am writing a server which will maintain thousands of SSL-encrypted simultaneous connections for a real-time application. If ONE panic makes its way to the top of the relevant goroutine, the whole process comes down, trashing all my connections in the process. It is non-trivial to reestablish them. Yes, my system is built to handle this case, but it's still not something I can afford to have happen every 15 seconds due to some error that is only affecting one out of my thousands of connections. (Also, I do understand why this is the only choice the Go runtime can make; this is not a complaint.)
At least in my world, every time I type "go", I must ensure that I am starting a goroutine that has some sensible top-level recover mechanism, and, honestly, for any Go program that actually plans on using concurrency, I think there's no alternative. You MUST handle panics. Why? Because an important aspect of Go's concurrency is maintaining composition of independent processes, and there are few things more uncompositional as a completely unrelated computation in a completely unrelated thread that trashes your entire OS process.
Panics may be for programming mistakes, but for any non-trivial code, you have some. Hopefully you can work out a better way of handling them than completely bringing the entire program down.
I am willing to assert that my code maintains enough isolation that continuing on is a reasonable thing to do. (It's a port of Erlang code anyhow. This is not a very freaky claim about such code.)
Sadly, you do, but Go makes it tolerable with "defer". Defer also produces nicer code.
Without defer, to be correct you would have to explicitly 'recover' (and re-panic?)
No, you don't. You should never be using recover as a matter of course.
You seem really hung-up on this point. Can you link to some code that illustrates your concerns?
That's just not true. There are very useful idioms for panic/recover, like when your code is profligate with errors (parsing, database work, etc.)
It's even used in the standard library: http://golang.org/src/pkg/text/template/exec.go#L93
(I use the pattern myself in certain situations. It's extremely useful.)
Note I said "as a matter of course". I agree it's useful in certain very limited circumstances, like parsing. But certainly not database work, unless you have a very different idea of what that entails than I do. Link to code?
It's the same principle as parsing. Database work involves lots of querying, scanning, etc. All of these operations produce errors. In the work that I do, the response to an error is usually, "rollback, show error to user." This makes it ideal for panic/recover. (And this can work well for either command line applications or web applications.)
Panicking isn't done when a database operation fails. Returning an error value is. It's just like old-school C. Panics are for programming errors or things like out of memory conditions, not errors in ordinary operation, even when components are failing.
Exactly.
I have exactly two panics in my ~15k line server. Both are in initialization code that will probably never get called, so it will fail very early on in the code. The rest of my code looks like this:
All the way down the stack. It's certainly a little more code, but it forces you to at least acknowledge all errors. If you want a stack-trace, you can always use the runtime package.No, this isn't what I'm talking about. See my response: https://news.ycombinator.com/item?id=7222197
I hate to be rude, but I feel like you jumped into this thread without reading the context.
I'm not talking about panicing instead of returning errors. I'm talking about using an idiom---which is used in the Go standard library (see my link up-thread)---to make error handling more terse when you're working with code that is otherwise profligate with checking errors.
At no point is a panic exposed to the user of a program or to the client of a library. At no point are errors ignored. The panics are kept within package and converted to error values.
Guarding your library boundary with a recover doesn't absolve your library internals from being nonidiomatic by using panics. (That the stdlib uses panic/recover in a few specific places does not make it broadly idiomatic.)
Without seeing specific code I can't say for sure, but it's very unlikely that any database interaction code is best modeled with panic/recover for error handling. I'm very curious to see the source, at this point.
Using panic/recover doesn't automatically make your code nonidiomatic.
That the stdlib uses panic/recover in several places is a good indicator that "never use panic/recover" is bad advice. Note that while I agree that just because something is in the stdlib doesn't mean it's idiomatic, I also cite that this particular approach is used to make the structure and organization of code more clear. Since it's used in several packages, I claim that this is a strong hint that panic/recover is appropriate in limited scenarios.
It's really not that hard to imagine. For example: http://play.golang.org/p/fhpRLd8EHY
We seem to have some wires crossed. Let's be clear, shall we?
* The panic/recover idiom is rarely used, but it is an idiom.
* There are trade-offs involved with using panic/recover. In my sample linked in this comment, many of the functions in database/sql need to be stubbed out so that they panic. However, the cost of this is relatively small, since it can mostly be isolated in a package.
* The idiom is most frequently seen in parsing because there are a lot of error cases to handle and the response to each error is typically the same.
* While parsing is the common scenario, I claim it is not the only one. I cite that database work is profligate with error checking, and depending on your application, there's a reasonable chance that the response to each error is going to be the same. When doing a lot of it, it can pay off to use the panic/recover idiom with similar benefits as for doing it with parsing.
* There may well be other scenarios where such handling is appropriate, but I have not come across them.
I've done an unusual amount of work with parsers and have done some database work, so I've had the opportunity to bump up against the panic/recover idiom a bit more than normal. As with anything else, it can be abused. But I find it extraordinarily useful in certain situations.
I've fixed some compile errors in my code snippet: http://play.golang.org/p/PLyMAD5ZvG --- sorry about that.
This is just another example of Go's fundamental attitude. Stuff is available for the language designers, but not for you :
* generic functions (e.g. append)
* generic data types (e.g. slices)
* exceptions (like illustrated above)
* special case syntax
* Custom event loops
* precompiler macros (very bad to use, horrible, blah blah ... except of course for the people imposing this restriction, and YES they're using it amongst other things to workaround the lack of generics in C)
...
This attitude was common in middle-90s "generic" programming languages like Ocaml, Modula-2 and others. You should simply look at Go as one of those languages and treat it as such.
If this attitude bothers you, you should look at C++0x and D.
I've read your comment twice and I cannot see any pertinent connection between it and what I said.
Agreed, you should be using defer, not recover.
The canonical examples are closing a file and releasing a mutex. Both have code samples here: http://blog.golang.org/defer-panic-and-recover
I'm confused as to what you guys are saying: are you saying that you don't need to handle exceptions (whether using defer or recover), or that it's better to use defer over recover? I take exception to the former, totally agree with the latter.
defer and recover have nothing to do with each other, except that in the few circumstances where it's appropriate to use recover, you often do it within a defer block.
Go doesn't have exceptions. You don't need to handle (i.e. explicitly deal with) panics via recover. If you do, especially if you're not making the panics yourself in e.g. a parsing package, that's a bad code smell and you're probably doing something wrong.defer doesn't "handle" panics. It won't stop your program from crashing. You have serious gaps in your knowledge on this subject.
My understanding is that defer is the broad equivalent of a Java finally block, and recover is the broad equivalent of a Java catch block. I think of both as ways of handling exceptions, although I see how the word "handle" could be interpreted in a way that makes my statements nonsensical. By handling I meant "doing the right thing", not "swallowing the panic/exception"; I apologize for the ambiguity you found.
If you do still think I have gaps in my knowledge, I humbly suggest that you briefly fill in those gaps with facts; it should save you time in the long run and will likely win you a few converts!
You're looking at it wrong. Stop thinking about Java. Go programmers rarely use recover. There are many that have probably never used it at all. Go does not have exceptions. Panics are only vaguely like exceptions, but you shouldn't even think about them in those terms. It is confusing you badly.
Defer is primarily used to make sure that clean-up happens in functions that have multiple exit points (return statements). It's a convenient side effect that defers are executed while a panic unwinds the stack, but it is rarely the first thing on the mind of the Go programmer when they type "defer".
Embrace error values. Return them! Check them! Panic when shit goes really bad. That's it. If you're writing Go code and you're thinking about "throw" "catch" or "finally", you're doing it wrong. Go's features do not map cleanly to those concepts, because Go doesn't have exceptions.
You say "setjmp", I say "thread library".
I think you're misunderstanding justinsb's point a little bit. To be concrete, you have to remember to use "defer" in Go to clean up resources and locks, or else someone trying to use "recover" won't handle panics properly.
This won't unlock the mutex on panic, which is observable if someone is trying to recover():
But this will: This is basically the same set of hazards as maintaining exception-safety in C++ or Java. So in this regard panic is very much like an exception system. (Of course, it has very different idiomatic use.)... To be concrete, you have to remember to use "defer" in Go to clean up resources and locks, ...
You should probably be using defer() all the time anyway, unless you have a good reason not to. It also helps with code evolution, in the cases where some yahoo adds a new return statement in the middle of a function.
Best practices for try/catch may be similar to idiomatic defer, but it's not the same semantically. For example, there's no analog to this:
If you don't use `catch`, then I could agree that try/finally is the same as defer, but I would argue that defer is a much cleaner design since cleanup code is located next to the thing they're cleaning up.I think it's much easier to audit this:
Than this: Other languages also make a distinction here, for example Python's `with` and D's `scope` [1].Also, it's trivial to make a `panic` that is unrecoverable: `go panic("broke your code, lol!!")`. This just cements the idea that `panic` is semantically different than exceptions, and should be treated as such.
[1] - http://dlang.org/statement.html#ScopeGuardStatement
You can do that by creating another function.
That's not different. In, say, Java, you can set the default uncaught exception handler to get the same behavior and then you can write:
You can't emulate the behavior of continuing the current block after an exception is caught. You have to recover() and copy/extract into a function any code that you'd want to run in the recover.
For example:
In Go, to run "other code", you'd have to duplicate all of that logic in the recover(): This isn't really the same thing, but I suppose you could technically get the same effect if you move all of "other code" into a function and called that in both places, but you're still duplicating code.Panics and exceptions are two very different things, which is why there are different idioms in place to make working with them safe.
Right. It's a pretty trivial transformation, and that's why it's not inaccurate to call panic/recover equivalent to exceptions: you can straightforwardly express every exception-based pattern using defer/panic/recover, and also the other way round. Sometimes you have to make more functions to make panic/recover work, but that's part of the "tied in with function declarations" nature of panic/recover/defer—there's nothing semantically that deep about it because the transformation is still quite simple.
I suppose that's technically true, so I'll have to concede the point. However, I still maintain that it's not practically true, since it has a very different flow than exceptions.
Anyway, I assume you're the same pcwalton from Rust? I really like the design of error handling so far, especially the bit about trapping conditions. From what I read, it looks failure just kills the task, instead of the entire program (like it does in go if unhandled).
I'm really looking forward to 1.0. Keep up the good work!