Error

The way handling errors in Go is quite different from other languages using special controlling keywords.
In Go, we should communicate with errors via an explicit way using the same language construct in normal tasks.

Well… Kind of wired, maybe.

package main
import (
 "errors"
 "fmt"
)
 
func f(i int) (int, error) {
 // By convention, errors are the last return value and have type `error`, a built-in interface.
 if i == 42 {
  return -1, errors.New("cannot work with 42")
  // Constructing a basic error with a string as message by calling `errors.New()`
 }
 return i + 3, nil
 // A `nil` on error position indicates no error
}
 
// We can predeclare variables as sentinel error used to signify a specific error.
var (
 ErrOutOfTea = errors.New("no more tea available")
 ErrPower    = errors.New("can't boil water")
)
 
func makeTea(arg int) error {
 switch arg {
 case 2:
  return ErrOutOfTea
 case 4:
  return fmt.Errorf("in method makeTea: %w", ErrPower) // `%w` for wrap, retaining the reference of error.
  // We can wrap errors with higher-level errors to add context.
  // Wrapped errors create a logical chain (A wraps B, which wraps C, etc.)
  // that can be queried with functions like `errors.Is` and `errors.AsType`.
 default:
  return nil
 }
}
 
func main() {
 for _, i := range []int{3, 42} {
  if r, err := f(i); err != nil { // It’s idiomatic to use an inline error check in the if line.
   fmt.Println("f failed: ", err)
  } else {
   fmt.Println("f succeeded: ", r)
  }
 }
 
 for i := range 5 {
  if err := makeTea(i); err != nil {
   // `errors.Is` checks that a given error (or any error in its chain) matches a specific error value.
   // Specially useful with wrapped or nested errors, allowing you to identify specific error types or sentinel errors in a chain of errors.
   if errors.Is(err, ErrOutOfTea) {
    fmt.Println("no tea")
   } else if errors.Is(err, ErrPower) {
    fmt.Println("no power")
   } else {
    fmt.Printf("unknown error: %s\n", err)
   }
   continue
  }
  fmt.Println("Done")
 }
}
 
/*
f succeeded:  6
f failed:  cannot work with 42
Done
Done
no tea
Done
no power
*/

Custom Error

package main
import (
 "errors"
 "fmt"
)
 
// A custom error type usually has the suffix “Error”.
type argError struct {
 arg     int
 message string
}
 
// Adding this Error method makes argError implement the error interface.
func (e *argError) Error() string {
 return fmt.Sprintf("%d - %s", e.arg, e.message)
}
 
func f(arg int) (int, error) {
 if arg == 42 {
  return -1, &argError{arg, "cannot work with"}
 }
 return arg + 3, nil
}
 
func main() {
 _, err := f(42)
 if ae, ok := errors.AsType[*argError](err); ok {
  // `errors.AsType` checks that a given error (or any error in its chain)
  // matches a specific error type and converts to a value of that type,
  // also returning true.
  // If there’s no match, the second return value is false.
  fmt.Println(ae.arg)
  fmt.Println(ae.message)
 } else {
  fmt.Println("error does not match argError")
 }
}
 
/*
42
cannot work with
*/

Panic

We use it when our programme confronted with some error we cannot or do not want to handle.

if err != nil {
 panic("panic")
}

Defer

Defer is used to ensure that a function call is performed later in a program’s execution,
usually for purposes of cleanup.

defer is often used where ensure and finally would be used in other languages.

package main
 
import (
 "fmt"
 "os"
 "path/filepath"
)
 
func main() {
 path := filepath.Join(os.TempDir(), "defer.txt")
 f := createFile(path)
 defer closeFile(f) // This will be executed at the end of the enclosing function (main), after writeFile has finished.
 writeFile(f)
}
 
func createFile(p string) *os.File {
 fmt.Println("creating")
 f, err := os.Create(p)
 if err != nil {
  panic(err)
 }
 return f
}
 
func writeFile(f *os.File) {
 fmt.Println("writing")
 fmt.Fprintln(f, "data")
}
 
func closeFile(f *os.File) {
 fmt.Println("closing")
 err := f.Close()
 if err != nil {
  panic(err)
 }
}
/*
creating
writing
closing
*/

Recover

Go makes it possible to recover from a panic, by using the recover built-in function.

package main
 
import "fmt"
 
func main() {
 // recover must be called within a deferred function.
 defer func() {
  if r := recover(); r != nil {
   // The return value of recover is the error raised in the call to panic.
   fmt.Println("recovered from: ", r)
  }
 }()
 
 panic("a problem")
 fmt.Println("After panic") // This is unreachable
}
/*
recovered from:  a problem
*/