Alex

Error Handling (Video 32)

Overview of Errors

type error interface {
  Error() string
}
type errType int
const (
  _ errType = iota
  noHeader
  invalidBody
)
type SomeError struct {
  kind errType
  pos int
  err error // some other underlying error
}

// implement the Error method for our new error
func (e SomeError) Error() {
  switch e.kind {
  case noHeader {
    return "No header"
  }
  case invalidBody {
    return "Invalid body"
  }
  }
}

// we can define our own methods on our custom error, e.g. building an error with value
func (e SomeError) with(pos int) SomeError {
  e1 = e
  e1.pos = pos
  return e1
}

// a useful pattern is to define "prototype" errors that we can then adapt using methods like above, e.g.
var (
  HeaderMissing = SomeError{kind: noHeader}
  BodyMissing = SomeError{kind: invalidBody}
)

// then if we have an invalid body we can specify a position for the error using the adapter:
BodyMissing.with(105)

Wrapped Errors

func (e *SomeError) Is(t error) bool {
  castErr, ok := t.(*SomeError)
  if !ok {
    return false
  }
  // here we check the internal kind of that errType defined above
  return castErr.kind == e.kind
}

errors.As()

if audio, err = DecodeWaveFile(fn); err != nil {
  var e os.PathError

  if errors.As(err, &e) {
    // ... do something with e
  }
}

Errors Philosophy

Using panic

Comparing to Exceptions

panic and recover

func abc() {
  panic("omg")
}

func main() {
  defer func() {
    if p := recover(); p != nil {
      // can't really do much else here other than print

      fmt.Println("recover: ", p)
    }
  }()

  abc()
}

Reducing Error Cases

Why?