When to use panic in Go?

1,470 views
Skip to first unread message

Christian

unread,
Nov 22, 2012, 5:28:10 AM11/22/12
to golan...@googlegroups.com
An article in the official Go blog suggests to use panic() only internally in packages:

"The convention in the Go libraries is that even when a package uses panic internally, its external API still presents explicit error return values."
http://blog.golang.org/2010/08/defer-panic-and-recover.html

But there are counterexamples like http://golang.org/pkg/regexp/#MustCompile So using panic should be ok, even in public functions, if there is no good way to continue work:

"One possible counterexample is during initialization: if the library truly cannot set itself up, it might be reasonable to panic, so to speak."
http://golang.org/doc/effective_go.html#panic

My extract from this is: use panic() for programming errors and otherwise error return values. I've written a simple router that must be configured with a kind of regex: router.Register("/<city>/<street>", myHandler) If the pattern in the first parameter is wrong, panic() is called. Do you agree with this usage of panic?

Another idea to use panic() is parameter checking like it is common in Java (http://code.google.com/p/guava-libraries/wiki/PreconditionsExplained). That could reduce the debugging time and lead to more robust code without the error handling code in trivial cases. Example:

func Register(path string, handler Handler) {
    if path == "" {
        panic("path must not be empty")
    }
    ...
}


What do you think of this kind of parameter checking?

panic() takes an empty interface as parameter and is usally called with a string. When would be useful to pass something else? And how can the developer get a useful error message then?

Regards
Christian

Chris Niekel

unread,
Nov 22, 2012, 6:03:19 AM11/22/12
to Christian, golang-nuts
If you take regexp as an example, you could have router.Register that returns an error, and a router.MustRegister(...) that panics.

As a precondition-failure, I'd return an error. That's like fmt.Sscanf with a "%" pattern.



Christian

--
 
 

Peter

unread,
Nov 22, 2012, 6:04:28 AM11/22/12
to golan...@googlegroups.com
I think you've got the right idea. The only alternative I've seen is using panic as a kind of multi-level recursive break (and I haven't done so as that just sounds complicated...).

regexp.MustCompile is intended for initialisation, usually with a string literal. If called during init() it acts as almost like a second compile-phase, to sanity check your regexes.

If your router is intended to be initialised once, with Register being called with string literals as in your example, I think a panic works well. If you're going to call Register throughout your program, with computed or user-given strings, I suggest using an error value.

I don't do the kind of parameter checking you suggest, as it just clutters the code too much for the benefits it brings (IMO). But various sanity checks are a good idea, in general, with panic a good way to alert you of them.

Panic can be called with an error type, which is anything with an Error() string method. Examples of how you can use errors to contain important information are here http://golang.org/doc/articles/error_handling.html, the same principles apply to panics.

As always in programming, there's no hard rule, but I believe this is the general consensus on how panic() is meant to be used. (Please correct if I'm mistaken).

André Moraes

unread,
Nov 22, 2012, 7:01:32 AM11/22/12
to Christian, golan...@googlegroups.com
> My extract from this is: use panic() for programming errors and otherwise
> error return values. I've written a simple router that must be configured
> with a kind of regex: router.Register("/<city>/<street>", myHandler) If the
> pattern in the first parameter is wrong, panic() is called. Do you agree
> with this usage of panic?
>
> Another idea to use panic() is parameter checking like it is common in Java
> (http://code.google.com/p/guava-libraries/wiki/PreconditionsExplained). That
> could reduce the debugging time and lead to more robust code without the
> error handling code in trivial cases. Example:

Don't do things that way, it will hide the possibility of an error,
the idea behind errors as values instead of exceptions is to force the
programmer to think when he is coding the use of that function instead
of simple allowing him to ignore the error handling code.

If you want a version of a function without the error handling, use
the MustXXX idiom, that way the user of your library is aware of a
possible panic (don't forget to document the function to explict
this).

How I would do it:

type Error string
func (e Error) Error() string {
return string(e)
}

const (
EmptyRoute = Err("Routes cannot be empty")
)

// Register the handler, the path cannot be empty.
//
// If the path is empty return EmptyRoute, otherwise return nil
func Register (path string, handler Handler) error {
if path == "" {
return EmptyRoute
}
// do processing
return nil
}

// Does the same thing Register does, but panic if an error happens
func MustRegister(path string, handler Handler) {
err = Register(path, handler)
if err != nil { panic(err) }
}


// User of your library

func main() {
// ...
// no reason to continue the program if can't register the handlers
MustRegister("/route1", handler1)
MustRegister("/route2', handler2)
// ...
// here the handlers can have a path defined by the user
// crazy requiriments...
if err = Register(userSuppliedRoute, handleUserSpecificThing); err {
log.Printf("Unable to load user route %v from database", userSuppliedRoute)
}
}

>
> func Register(path string, handler Handler) {
> if path == "" {
> panic("path must not be empty")
> }
> ...
> }
>

--
André Moraes
http://amoraes.info

André Moraes

unread,
Nov 22, 2012, 7:03:15 AM11/22/12
to Christian, golan...@googlegroups.com
> // ...
> // here the handlers can have a path defined by the user
> // crazy requiriments...
> if err = Register(userSuppliedRoute, handleUserSpecificThing); err {
> log.Printf("Unable to load user route %v from database", userSuppliedRoute)
> }
> }

The if should be:
if err = Register(userSuppliedRoute, handleUserSpecificThing); err != nil {
// ... body

Dustin Sallings

unread,
Nov 22, 2012, 5:52:48 PM11/22/12
to golan...@googlegroups.com
Christian <chri...@helmbold.de> writes:

> "One possible counterexample is during initialization: if the library
> truly cannot set itself up, it might be reasonable to panic, so to
> speak."
> http://golang.org/doc/effective_go.html#panic

In some of my code, I broaden the "initialization" concept to anything
that only go wrong when the only failure possible is by the programmer
misusing the API. These things can typically trivially be picked up at
unit test time (e.g. if your package init doesn't run because of a
MustCompile, then you'll know it early).

For example, go-probably allows you to create count-min sketches with
dimensions you provide based on your requirements. If you try to make a
sketch that's not at least one element wide or tall, your program
couldn't possibly work and I just panic there.

As I implied above, I *would* return an error if the function already
returned an error. i.e. I only would use panic if the error is easily
avoidable and the function couldn't fail otherwise.

You'll see similar usage in math/big's divide functions, for example.
If you try to divide by zero, it'll panic, because that's the only thing
you can't divide by, the only reason the function would have to error,
and you just shouldn't do that.

--
dustin

Reply all
Reply to author
Forward
0 new messages