Chapter 13 of 20
Error Handling
The error interface, errors.Is/As, panic/recover
The error Interface
Go doesn't use exceptions; a function returns errors explicitly as its last return value. error is an interface with a single Error() string method.
package main
import (
"fmt"
"os"
)
func readSize(path string) (int64, error) {
f, err := os.Open(path)
if err != nil {
return 0, fmt.Errorf("open %s: %w", path, err) // wrap with %w
}
defer f.Close()
info, err := f.Stat()
if err != nil {
return 0, err
}
return info.Size(), nil
}
func main() {
n, err := readSize("/etc/hostname")
fmt.Println(n, err)
}errors.Is and errors.As
The standard errors package provides matching tools that see through multiple layers of %w wrapping.
package main
import (
"errors"
"fmt"
"io/fs"
"os"
)
func main() {
_, err := os.Open("/no/such/file")
if errors.Is(err, fs.ErrNotExist) {
fmt.Println("file missing")
}
var pe *fs.PathError
if errors.As(err, &pe) {
fmt.Println("path:", pe.Path, "op:", pe.Op)
}
}Custom Error Types
package main
import "fmt"
type ValidationError struct {
Field, Reason string
}
func (e *ValidationError) Error() string {
return e.Field + ": " + e.Reason
}
func validate(name string) error {
if name == "" {
return &ValidationError{Field: "name", Reason: "empty"}
}
return nil
}
func main() {
fmt.Println(validate(""))
}panic / recover
panic is a real "exception" that unwinds the call stack. Don't use it unless you genuinely cannot continue. recover only catches a panic when called inside a defer.
package main
import "fmt"
func risky() {
panic("boom")
}
func safe() (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("panic: %v", r)
}
}()
risky()
return
}
func main() {
fmt.Println(safe())
}