4 ms·
On the topic of when to use error-returns vs. when to use panic: I struggled with this aspect of Go coding too and what I decided was that, in order to choose
by ericbb 14y ago
On the topic of when to use error-returns vs. when to use panic:
I struggled with this aspect of Go coding too and what I decided was that, in order to choose your approach to errors, you should think about how important it is that your function can be composed into an expression:
x := foo(a) + bar(b)
vs.
c, err := foo(a)
if err != nil {
...
}
d, err := bar(b)
if err != nil {
...
}
x := c + d
It's a trade-off. By using "panic", you enable more concise and compositional code. By using error-returns, you are being more explicit and making it easier for the caller to handle errors at the call-site.
Usually, functions that can fail are not the kind you desperately want to use in expressions anyway. So error-returns are more common. But there are many situations where errors can happen and where composition is a big win. In those cases, you reach for "panic".
If you look at some of the built-ins that can panic--I'm thinking of array-indexing and division and regexp.MustCompile(), for example--you notice that these would become very cumbersome if they used error-returns instead.
There's also the issue of initialization. That's the justification given in the case of regexp.MustCompile(). An initializer must be a single expression.
The words "panic" and "error" probably lead people to think that you should make the decision based on severity but I don't think that's right.
(That's just my take on it. I'm no Go expert so there are likely better ways to think about it.)
- wonderzombie 14y agoAs I understand it, it is about severity. I think "panic" and "error" are meant to handle situations where there is no obvious, correct answer, like with array indexing. A nil pointer is another gimme example. Some languages have unchecked exceptions — maybe conceptualize it somewhat like that. panics() are for disasters, for serious program errors. By contrast, a network timeout is not a disaster. It's not "normal" or desired behavior but it's well within widely-known modes of behavior. Composition is nice-to-have but in general you ought to be able to write Go as if everything that does not return errors will succeed under the vast majority of circumstances, or where failure is inevitable. Recover is, among other things, a failsafe of sorts for cases when code you do not control (e.g. a library) panics. It might be catastrophic failure for the library, but it may not be for your code, and you need an escape hatch. ...I am not an expert, either, but I've spent a fair amount of time on the Go mailing lists. I feel like I'm actually repeating something I've read, but Effective Go doesn't talk much about this. (It does say that library functions should avoid panic.)
- luriel 14y agoPanic() should only be used to signal either programmer error, or truly panic-worthy situations where state is so messed up that crashing is the only good option. Put another way: when you call an API correctly, it should be safe to assume it will never panic(). Panic/recover can be used in rare occasions within libraries to do more exception-ish style error handling, but those panics should never be allowed to escape and cross API boundaries.
- ericbb 14y agoI would argue that there is an obvious correct answer for array indexing and nil pointer dereference. Go could have been designed to work like the following: x, err := *p Where err is nil unless p is nil. And for array indexing: x, err := a[n] The runtime could do its bounds check and return (zero, IndexOutOfBoundsError) if the check fails, where zero is the zero-value for the type. It seems to me that these solutions are perfectly workable except for the massive code-size/verbosity explosion they would induce. In such cases, the code should effectively prove that p is not nil and n is within bounds before performing the risky operations. Maybe a good plan is to always validate input first so that you can write expression-oriented code that only fails in the case of programmer error. A situation from my experience was a recursive transformation where an intermediate call had no good way to deal with an error except pass it along to its caller--so I used a panic within the package for that. In hindsight, I think a better solution may have been to validate the input in an earlier pass so that the recursive transformation should always succeed. So, in cases where the program can validate inputs first, it should do so and then be free to use compositional code that panics when the validation was broken. In cases where validation cannot remove error conditions, error-returns should be used. Reserving panic for programmer-error, as luriel recommends in his reply, seems like a good maxim. I think I'll try to use that from now on.