Go provides a very simple error handling mechanism through its built-in error interface.
Go's error handling uses explicit error returns rather than traditional exception handling mechanisms. This design makes code logic clearer and allows developers to handle errors explicitly at compile time or runtime.
Go's error handling mainly revolves around the following mechanisms:
errorinterface: The standard error representation.- Explicit return values: Errors are returned through function return values.
- Custom errors: Errors can be created via standard library or custom methods.
panicandrecover: For handling unrecoverable serious errors.
error interface
The Go standard library defines an error interface that represents an abstraction of an error.
The error type is an interface type. Its definition is:
type error interface {
Error() string
}
- Implementing the
errorinterface: Any type that implements theError()method can be used as an error. - The
Error()method returns a string describing the error.
Creating errors with the errors package
We can generate error messages in code by implementing the error interface type.
Creating 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 typically return error information in the last return value. Using errors.New can return an error message:
func Sqrt(f float64) (float64, error) {
if f < 0 {
return 0, errors.New("math: square root of negative number")
}
// implementation
}
In the example below, we pass a negative number when calling Sqrt, and then get a non-nil error object. Comparing this object with nil yields true, so fmt.Println (the fmt package calls the Error method when handling errors) is invoked to output the error. Please see the following example code:
result, err := Sqrt(-1)
if err != nil {
fmt.Println(err)
}
Explicit error returns
In Go, errors are usually returned as function return values. Developers need to explicitly check and handle them.
Explicit error return:
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 errors
By defining custom types, you can extend the error interface.
Custom error type:
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
The fmt package provides support for formatted output of errors:
%v: Default format.%+v: Shows detailed error information if supported.%s: Output as a string.
Example
package main
import (
"fmt"
)
// Define a DivideError structure
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 integer 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)
}
// When the divisor is zero, an error message is returned
if _, errorMsg := Divide(100, 0); errorMsg != "" {
fmt.Println("errorMsg is: ", errorMsg)
}
}
Executing 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, the errors package introduced errors.Is and errors.As for handling error chains:
errors.Is
Checks whether an error is a specific error or wrapped by 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
Converts an error to a specific type for further handling.
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 output stack information.
- Commonly used when the program cannot continue running.
recover:
- Catches a
panicto prevent the program from crashing.
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")
}
Executing the above code, the output is:
Starting program...
Recovered from panic: something went wrong
Program continued after panic
YouTip