Go Error Handling

The Go language provides a very simple error handling mechanism through its built-in error interface.

Go's error handling uses an explicit error return approach rather than a traditional exception-handling mechanism. This design makes the code logic clearer and makes it easier for developers to handle errors explicitly during compilation or execution.

Go's error handling mainly revolves around the following mechanisms:

  1. errorinterfaceThe standard error representation.
  2. Explicit return valueErrors are returned through the function's return value.
  3. Custom error: Errors can be created through the standard library or in a custom way.
  4. panicandrecoverHandle unrecoverable fatal errors.

error interface

The Go standard library defines an error interface, representing an abstraction of an error.

The error type is an interface type. Here is its definition:

type error interface {
    Error() string
}
  • Implementationerrorinterface: Anything that implementsError()Any type that implements the Error() method can be used as an error.
  • Error()The method returns a string describing the error.

Using the errors package to create errors

In coding, we can generate error information by implementing the error interface type.

Create a simple error:

Example

package main

import (
    "errors"
    "fmt"
)

func main() {
    err := errors.New("this is an error")
    fmt.Println(err) // Output: this is an error
}

Functions usually return error information as the last return value. You can use errors.New to return an error message:

func Sqrt(f float64) (float64, error) {
    if f < 0 {
        return 0, errors.New("math: square root of negative number")
    }
    // 实现
}

In the following example, we pass a negative number to Sqrt, and then get a non-nil error object. Comparing this object with nil returns true, so fmt.Println is invoked to output the error (when the fmt package handles an error, it calls the Error method). See the example call code below:

result, err:= Sqrt(-1)

if err != nil {
   fmt.Println(err)
}

Explicitly return errors

In Go, errors are usually returned as return values from functions, and developers need to explicitly check and handle them.

Explicitly return errors:

Example

package main

import (
        "errors"
        "fmt"
)

func divide(a, b int) (int, error) {
        if b == 0 {
                return 0, errors.New("division by zero")
        }
        return a / b, nil
}

func main() {
        result, err := divide(10, 0)
        if err != nil {
                fmt.Println("Error:", err)
        } else {
                fmt.Println("Result:", result)
        }
}

Output:

Error: division by zero

Custom error

By defining custom types, you can extend the error interface.

Custom error types:

Example

package main

import (
        "fmt"
)

type DivideError struct {
        Dividend int
        Divisor  int
}

func (e *DivideError) Error() string {
        return fmt.Sprintf("cannot divide %d by %d", e.Dividend, e.Divisor)
}

func divide(a, b int) (int, error) {
        if b == 0 {
                return 0, &DivideError{Dividend: a, Divisor: b}
        }
        return a / b, nil
}

func main() {
        _, err := divide(10, 0)
        if err != nil {
                fmt.Println(err) // Output: cannot divide 10 by 0
        }
}

The fmt package and error formatting.

fmtThe package provides formatted output support for errors:

  • %v: Default format.
  • %+v: If supported, display detailed error information.
  • %sOutput as a string.

Example

package main

import (
    "fmt"
)

// Define a DivideError struct
type DivideError struct {
    dividee int
    divider int
}

// Implement the `error` interface
func (de *DivideError) Error() string {
    strFormat := `
    Cannot proceed, the divider is zero.
    dividee: %d
    divider: 0
`

    return fmt.Sprintf(strFormat, de.dividee)
}

// Define a function for `int` type division
func Divide(varDividee int, varDivider int) (result int, errorMsg string) {
    if varDivider == 0 {
            dData := DivideError{
                    dividee: varDividee,
                    divider: varDivider,
            }
            errorMsg = dData.Error()
            return
    } else {
            return varDividee / varDivider, ""
    }

}

func main() {

    // Normal case
    if result, errorMsg := Divide(100, 10); errorMsg == "" {
            fmt.Println("100/10 = ", result)
    }
    // Returns an error message when the divisor is zero
    if _, errorMsg := Divide(100, 0); errorMsg != "" {
            fmt.Println("errorMsg is: ", errorMsg)
    }

}

Run the above program, the output is:

100/10 =  10
errorMsg is:  
    Cannot proceed, the divider is zero.
    dividee: 100
    divider: 0


Using errors.Is and errors.As

Starting from Go 1.13,errorsThe package introduceserrors.Isanderrors.AsUsed for handling error chains:

errors.Is

Check whether an error is a specific error or was wrapped from that error.

Example

package main

import (
        "errors"
        "fmt"
)

var ErrNotFound = errors.New("not found")

func findItem(id int) error {
        return fmt.Errorf("database error: %w", ErrNotFound)
}

func main() {
        err := findItem(1)
        if errors.Is(err, ErrNotFound) {
                fmt.Println("Item not found")
        } else {
                fmt.Println("Other error:", err)
        }
}

errors.As

Convert the error to a specific type for further processing.

Example

package main

import (
        "errors"
        "fmt"
)

type MyError struct {
        Code int
        Msg  string
}

func (e *MyError) Error() string {
        return fmt.Sprintf("Code: %d, Msg: %s", e.Code, e.Msg)
}

func getError() error {
        return &MyError{Code: 404, Msg: "Not Found"}
}

func main() {
        err := getError()
        var myErr *MyError
        if errors.As(err, &myErr) {
                fmt.Printf("Custom error - Code: %d, Msg: %s\n", myErr.Code, myErr.Msg)
        }
}

panic and recover

Go's panic is used to handle unrecoverable errors, and recover is used to recover from a panic.

panic:

  • Causes the program to crash and outputs stack information.
  • Often used when the program cannot continue running.

recover:

  • catchpanicavoiding program crashes.

Example

package main

import "fmt"

func safeFunction() {
        defer func() {
                if r := recover(); r != nil {
                        fmt.Println("Recovered from panic:", r)
                }
        }()
        panic("something went wrong")
}

func main() {
        fmt.Println("Starting program...")
        safeFunction()
        fmt.Println("Program continued after panic")
}

Run the above code, the output is:

Starting program...
Recovered from panic: something went wrong
Program continued after panic
other extensions