author
Kevin Kelche

Understanding Errors in Go: Effective Handling Techniques


Introduction

In Go, errors are a fundamental part of the language. Unlike in other languages where error handling is an afterthought, in Go, errors can be referred to as first-class citizens. Error handling refers to the process of anticipating, detecting, and responding to errors in a program. Error handling is a breeze in Go, and it is one of the reasons why Go programs are robust and reliable.

Go has a built-in error type and uses multiple return values to return both the result and the error. This approach allows you to handle errors cleanly and concisely without the need for try-catch blocks. Additionally, Go provides panic, recover, and defer for advanced error handling.

In this article, we will explore the details of errors in Go, including the different types of errors, Go’s error handling approach, common error handling techniques, and best practices for error handling in Go.

Creating Errors

There are two ways to create errors in Go: using the errors.New function and using the fmt.Errorf function.

errors.New

The errors.New function creates a new error with the given error message.

main.go
package main

import (
    "errors"
    "fmt"
)

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

Copied!

fmt.Errorf

The fmt.Errorf function creates a new error with the given error message and formatting directives.

main.go
package main

import (
    "fmt"
)

func main() {
    err := fmt.Errorf("some error: %s", "some error message")
    fmt.Println(err)
    // Output: some error: some error message
}

Copied!

Types of Errors

Errors can be classified into two categories: Standard library errors and custom errors.

Standard Library Errors

These are predefined errors that are provided by the Go standard library. These errors are used to indicate common error conditions, such as file not found, invalid argument, invalid operation, and so on. The standard library errors are defined in the errors package.

main.go
package main

import (
    "os"
    "fmt"
)

func main() {
  file, err := os.Open("non_existent_file.txt")
  if err != nil {
    fmt.Println(err)
    // Output: open non_existent_file.txt: no such file or directory
  }
}

Copied!

Custom Errors

These are errors that are defined by you the developer. They are used to indicate specific error conditions that are unique to your application. For instance, you can define a custom error to indicate that a user is not authorized to perform a certain action.

main.go
package main

import (
    "fmt"
)

type UserNotAuthorizedError struct {
    message string
}

func (e *UserNotAuthorizedError) Error() string {
    return e.message
}

func someFunction() error {
    return &UserNotAuthorizedError{message: "User is not authorized to perform this action"}
}

func main() {
    err := someFunction()
    if err != nil {
        fmt.Println(err)
        // Output: User is not authorized to perform this action
    }
}

Copied!

In the example above, we defined a custom error type UserNotAuthorizedError that implements the Error interface. The error interface is defined as follows:

builtin.go
type error interface {
    Error() string
}

Copied!

The Error method is implemented by the UserNotAuthorizedError type, and it returns the message field of the UserNotAuthorizedError struct.

Error Handling

We have covered error interface and handling errors with multiple return values. In this section, we will cover the panic, recover, and defer functions.

main.go
package main

import (
    "fmt"
)

func main() {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println(err)
            // Output: some error
        }
    }()

    panic("some error")
}

Copied!

Error Handling Techniques

Error Wrapping

This technique involves wrapping an error with additional context. This is useful when you want to provide more information about the error to the caller. For instance, in an http handler, you can wrap the error with additional context such as more information about the error. fmt.Errorf is useful for error wrapping.

main.go
package main

import (
    "fmt"
    "net/http"
)

func startServer() error {
    return http.ListenAndServe(":8080", nil)
}

func main() {
    err := startServer()

    if err != nil {
        fmt.Println(fmt.Errorf("error starting server: %w", err))
    }
}

Copied!

If you run the code above in a state the port 8080 is already in use, you will get the following output:

terminal
error starting server: listen tcp :8080: bind: address already in use

Copied!

Error Handling Best Practices

Conclusion

To wrap it up! In this article, we covered the different types of errors in Go, Go’s error handling approach, common error handling techniques, and best practices for error handling in Go.

Subscribe to my newsletter

Get the latest posts delivered right to your inbox.