Go Custom Errors

Introduction

In Go, error handling is explicit and uses the built-in error interface. While Go provides several ways to create errors, including using the errors package, there are situations where you might want to create custom error types to provide more context or additional information about the error. In this chapter, you will learn how to create, use, and handle custom errors in Go.

The error Interface

The error interface in Go is defined as:

type error interface {
    Error() string
}

Any type that implements the Error() method with this signature satisfies the error interface and can be used as an error.

Creating Custom Errors

To create a custom error, you define a struct to hold the error details and implement the Error() method for that struct.

Example: Defining a Custom Error

Example:

package main

import (
    "fmt"
)

// Define a custom error type
type MyError struct {
    Code    int
    Message string
}

// Implement the Error() method for the custom error type
func (e *MyError) Error() string {
    return fmt.Sprintf("Error %d: %s", e.Code, e.Message)
}

func main() {
    // Create an instance of the custom error
    err := &MyError{Code: 404, Message: "Resource not found"}

    // Use the error
    fmt.Println(err)
}

Output:

Error 404: Resource not found

Using Custom Errors

You can use custom errors in the same way as built-in errors, by returning them from functions and checking for them in the calling code.

Example: Returning and Checking Custom Errors

Example:

package main

import (
    "fmt"
)

// Define a custom error type
type MyError struct {
    Code    int
    Message string
}

// Implement the Error() method for the custom error type
func (e *MyError) Error() string {
    return fmt.Sprintf("Error %d: %s", e.Code, e.Message)
}

// Function that returns a custom error
func doSomething() error {
    return &MyError{Code: 500, Message: "Internal Server Error"}
}

func main() {
    err := doSomething()
    if err != nil {
        if customErr, ok := err.(*MyError); ok {
            fmt.Printf("Custom error occurred: %d - %s\n", customErr.Code, customErr.Message)
        } else {
            fmt.Println("An error occurred:", err)
        }
    }
}

Output:

Custom error occurred: 500 - Internal Server Error

Wrapping Errors with Additional Context

Go 1.13 introduced error wrapping, which allows you to wrap errors with additional context using fmt.Errorf and the %w verb. This can be combined with custom errors to provide more detailed error information.

Example: Wrapping Custom Errors

Example:

package main

import (
    "fmt"
    "errors"
)

// Define a custom error type
type MyError struct {
    Code    int
    Message string
}

// Implement the Error() method for the custom error type
func (e *MyError) Error() string {
    return fmt.Sprintf("Error %d: %s", e.Code, e.Message)
}

// Function that returns a wrapped custom error
func doSomething() error {
    baseErr := &MyError{Code: 500, Message: "Internal Server Error"}
    return fmt.Errorf("failed to do something: %w", baseErr)
}

func main() {
    err := doSomething()
    if err != nil {
        var customErr *MyError
        if errors.As(err, &customErr) {
            fmt.Printf("Custom error occurred: %d - %s\n", customErr.Code, customErr.Message)
        } else {
            fmt.Println("An error occurred:", err)
        }
    }
}

Output:

Custom error occurred: 500 - Internal Server Error

Advanced Custom Error Handling with Context and Stack Traces

For more advanced error handling, you might want to include additional context or even stack traces in your custom errors. There are third-party libraries, such as pkg/errors, that provide enhanced error handling features.

Example: Using pkg/errors for Stack Traces

Example:

package main

import (
    "fmt"
    "github.com/pkg/errors"
)

// Define a custom error type
type MyError struct {
    Code    int
    Message string
}

// Implement the Error() method for the custom error type
func (e *MyError) Error() string {
    return fmt.Sprintf("Error %d: %s", e.Code, e.Message)
}

// Function that returns a wrapped custom error with stack trace
func doSomething() error {
    baseErr := &MyError{Code: 500, Message: "Internal Server Error"}
    return errors.Wrap(baseErr, "failed to do something")
}

func main() {
    err := doSomething()
    if err != nil {
        fmt.Printf("An error occurred: %+v\n", err)
    }
}

Output:

An error occurred: failed to do something: Error 500: Internal Server Error
main.doSomething
	/path/to/file.go:15
main.main
	/path/to/file.go:20
runtime.main
	/usr/local/go/src/runtime/proc.go:203

Conclusion

Creating and using custom errors in Go allows you to provide more detailed and specific error information in your applications. By defining custom error types and implementing the Error() method, you can tailor error messages to suit your needs. Additionally, by leveraging features like error wrapping and third-party libraries, you can enhance your error handling to include context and stack traces, making it easier to diagnose and fix issues in your code.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top