Panic on err: Syntax Proposal

Source from: Go by Example: Reading Files

func check(e error) {
    if e != nil {
        panic(e)
    }
}

func main() {
    dat, err := os.ReadFile("/tmp/dat")
    check(err)

I think it would be great, if golang would provide a shorter way to “panic on err”.

What about this syntax?

func main() {
    dat, .. := os.ReadFile("/tmp/dat")

If you have a lot of time (and your employer a lot of money), then
it is fine to care about every err in detail.

But if you are creating a MVP where you just want to test your assumptions,
then it would be nice to a simple way to panic if err is not nil.

I have been thinking about this for some days, and think that the above syntax
is easy to read, and would simplify golang.

What do you think?

I doubt that this change would be added to the language for two main reasons:

  1. Now that Go has generics, you can almost get all the functionality with functions like this:

    func must[T any](value T, err error) T {
        if err != nil {
            panic(err)
        }
        return value
    }
    
    func main() {
        dat := must(os.ReadFile("/tmp/dat"))
    

    Note that for functions with, e.g. 2 return values, you’ll need to write a must2 version of the func, etc.

  2. Go is all about explicit error handling. The language designers don’t consider all the if err != nil checks to be a language design flaw, but actually consider it a feature. Just like you can do things with “normal” return values, you also do things (or explicitly choose not to do things) with returned errors. Throwing exceptions/panicking is viewed the same as goto in that it is hard to follow the control flow and Go is all about obvious, even boring code that is easy to read or even skim through.

    Consider also that if you had to eventually move the logic you’re doing in main into a separate package, you would want to change all of your panics into returning errors so that users of your package can check errors instead of guarding every call into the package with some construct like defer func() { v := recover(); ... }(). If you’re using your proposed , .. := syntax or a must-like function, you would have to go through the code and change it all to checking and returning errors (or you would have to write a program that goes through the AST and rewrites it for you).

Perhaps I’ve been using Go too long and am now brainwashed into only seeing the positives (Stockholm syndrome? :thinking: :laughing:) but I would argue that it’s usually good to keep the errors when prototyping. Sometimes while I’m putting something together, I’ll ask myself, “oh yea, but where do I get this contextual information from?” or “What should happen when this goes wrong?” etc..

2 Likes

No, you’re not :slight_smile:

For @guettli’s MVP scenario, the generic must() approach seems perfect.

Otherwise, panicking should remain the absolute exception, used only in cases where there is no sane way of continuing after an error happens.

Explicit error handling is indeed something to get used to, but the benefits of clearly seeing the execution paths in both the good and the error cases outweigh the added verbosity.

1 Like

Otherwise, panicking should remain the absolute exception, used only in cases where there is no sane way of continuing after an error happens.

it depends on the use case. Imagine the use case application development: you handle http requests. Authentication was already done, now I check if the request has the matching permissions (via SQL), then I fetch some data from a SQL database. Both steps can do literally nothing if the DB is unreachable. Responsing with 500 “Internal Server Error” is perfectly fine, if the DB is down. I think “panic on err” is fine here.

I see it your way, if you write a library. There “panic on err” makes no sense. But for application development, I think “panic on err” is ok.

I would still argue against using panic here. The database being unreachable could be a temporary problem; networks aren’t 100% reliable, but if you panic, that will crash your whole application, not just the goroutine handling the request unless perhaps if you’re deferring a call to recover somewhere in your middleware. If your application is doing any work in any other goroutine that isn’t talking to the database at the moment that you attempt to check those permissions, the panic will bring the whole process down and leave that other worker goroutine’s data in an unknown state.

1 Like

I am playing around with http.ListenAndServe(), and this seem to handle a panic in a http-handler. The server keeps on running if one http request results in a panic.

I still think the pragmatic .. to panic on err makes sense.

Of course it does not make sense if you write a re-usable library. There it makes no sense.

But if you write a http request handler, then I think this is fine, since the http server handles the panic. The panic created by .. does not terminate the server, it just terminates the current http request.

It is much more idiomatic to write a MustReadFile helper than that does the panic for you.

For me, as a go application developer, there are several thousand functions available which I could call.

Many of them return err as last argument. I don’t think it makes sense to double the number of functions, and create a MustFoo() function for every function.

It is a burden to keep in mind which function have a Must...() version and which don’t.

I though Go was about reducing cognitive load.

And exactly thats why implicit panic will not happen.

Implicit behaviour always means extra cognitive load.

Panics in general should not happen at all. They are not meant to be used to control the flow of your program.

If reading the file gives an error during an HTTP request, you do not want that request to just barf into your logs and give your user a generic 50x response. You want to tell the user what is going on, and that its not their fault, in a way they understand.

1 Like