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
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!