58

I want to return an error from a function if it panics (in Go):

func getReport(filename string) (rep report, err error) {
    rep.data = make(map[string]float64)

    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered in f", r)
            err, _ = r.(error)
            return nil, err
        }
    }()
    panic("Report format not recognized.")
    // rest of the getReport function, which can try to out-of-bound-access a slice
    ...
} 

I appear to have misunderstood the very concept of panic and defer. Can anybody enlighten me?

030
  • 10,842
  • 12
  • 78
  • 123
jrichner
  • 699
  • 1
  • 5
  • 9
  • panic is an exception handling system but Go's way of dealing with faulty conditions is designed in a way that you should not use panic. Think of panic as the equivalent to a RuntimeException in java – panic only when you're not supposed to deal with the error. – fuz Nov 12 '13 at 16:39
  • 2
    Also, The function you call with defer is a function on its own. It's return value will not affect the surrounding function and will be discarded instead. – fuz Nov 12 '13 at 16:46
  • 3
    It's worth noting for the record here that while panics are intended to be cases that do not generally require "catching", I've seen multiple scenarios where something panics deep in someone else's code (a library with a bug in it, e.g.) and it should not exit your application but rather just be reported as a failure of the library to do its thing properly. The sensible thing to do is recover from the panic and return it as an error. It's not ideal, but given the situation, it seems to make good sense. – Brad Peabody Oct 09 '17 at 06:01

3 Answers3

81

In a deferred function you can alter the returned parameters, but you can't return a new set. So a simple change to what you have will make it work.

There is another problem with what you wrote, namely that the you've paniced with a string but are expecting an error in your type assertion.

Here is a fix for both of those (Play)

defer func() {
    if r := recover(); r != nil {
        fmt.Println("Recovered in f", r)
        // find out exactly what the error was and set err
        switch x := r.(type) {
        case string:
            err = errors.New(x)
        case error:
            err = x
        default:
            err = errors.New("Unknown panic")
        }
        // invalidate rep
        rep = nil
        // return the modified err and rep
    }
}()
Nick Craig-Wood
  • 52,955
  • 12
  • 126
  • 132
7

have a look at this

package main

import "fmt"

func iWillPanic() {
    panic("ops, panic")
}
func runner() (rtnValue string) {
    rtnValue := ""
    defer func() {
        if r := recover(); r != nil {
            // and your logs or something here, log nothing with panic is not a good idea
            rtnValue = "don't panic" // modify the return value, and it will return
        }
    }()
    iWillPanic()
    return rtnValue
}

func main() {
    fmt.Println("Return Value:", runner())
}
HaileyStorm
  • 33
  • 1
  • 7
fang jinxu
  • 319
  • 1
  • 3
  • 11
3
func TestReturnFromPanic(t *testing.T) {
   fn := func(filename string) (rep string, err error) {
       defer func() {
           if r := recover(); r != nil {
               err = fmt.Errorf("panic in getReport %s", r)
           }
       }()
       return filename[100:], nil
   }
   t.Log(fn(``))
}

The named return parameter err is the trick.

https://play.golang.org/p/jpaCa9j2iAf

ZAky
  • 1,209
  • 8
  • 22