DEV Community

Cover image for Error handling in Golang
Tirumal Rao
Tirumal Rao

Posted on

Error handling in Golang

Error handling in Go is somewhat unique compared to some other programming languages. Instead of using exceptions, Go uses error values to indicate an abnormal state. This design promotes explicit error checking and can lead to more robust code.

1. The error Type:

Go has a built-in interface type called error. This interface has a single method Error() string.

type error interface {
    Error() string
}
Enter fullscreen mode Exit fullscreen mode

Any type that implements this method can be used as an error.

2. Returning Errors:

It's idiomatic in Go to return errors from functions and methods. A common pattern is to return both a result and an error, and to check the error immediately:

func SomeFunction() (ResultType, error) {
    if somethingWrong {
        return nil, errors.New("something went wrong")
    }
    return result, nil
}

result, err := SomeFunction()
if err != nil {
    // handle the error
}
Enter fullscreen mode Exit fullscreen mode

3. Custom Error Types:

Custom error types can be particularly useful when you want to provide more information about an error or categorize errors in a way that allows the caller to programmatically react to different error scenarios.

Here's an example of how you might use custom error types in Go:

package main

import (
    "fmt"
)

// NotFoundError represents an error when something is not found.
type NotFoundError struct {
    EntityName string
}

func (e *NotFoundError) Error() string {
    return fmt.Sprintf("%s not found", e.EntityName)
}

// PermissionError represents an error when permissions are denied.
type PermissionError struct {
    OperationName string
}

func (e *PermissionError) Error() string {
    return fmt.Sprintf("permission denied for %s", e.OperationName)
}
Enter fullscreen mode Exit fullscreen mode

Using and Checking Custom Error Types:

func FindEntity(name string) (string, error) {
    // Simulating some logic
    if name == "missingEntity" {
        return "", &NotFoundError{EntityName: name}
    }
    return "EntityData", nil
}

func PerformOperation(name string) error {
    // Simulating some logic
    if name == "restrictedOperation" {
        return &PermissionError{OperationName: name}
    }
    return nil
}

func main() {
    _, err := FindEntity("missingEntity")
    if err != nil {
        switch e := err.(type) {
        case *NotFoundError:
            fmt.Println("Handle not found:", e.Error())
        case *PermissionError:
            fmt.Println("Handle permission error:", e.Error())
        default:
            fmt.Println("Handle general error:", e.Error())
        }
    }

    err = PerformOperation("restrictedOperation")
    if err != nil {
        switch e := err.(type) {
        case *NotFoundError:
            fmt.Println("Handle not found:", e.Error())
        case *PermissionError:
            fmt.Println("Handle permission error:", e.Error())
        default:
            fmt.Println("Handle general error:", e.Error())
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

In this example, we have two custom error types: NotFoundError and PermissionError. When we encounter these errors in our functions (FindEntity and PerformOperation), we return instances of these types. In our main function, we can use a type switch to handle different types of errors in different ways.

This approach provides flexibility and clarity in error handling, and allows callers to react differently based on the type of error encountered.

4. The fmt Package for Errors:

The fmt package can be used to format error strings. The Errorf function is particularly handy for this:

return fmt.Errorf("error occurred while processing %s", itemName)
Enter fullscreen mode Exit fullscreen mode

5. Wrapping Errors:

As of Go 1.13, the standard library added support for wrapping errors using the %w verb with fmt.Errorf. This allows you to wrap an error with additional context, while preserving the original error:

if err != nil {
    return fmt.Errorf("failed processing: %w", err)
}
Enter fullscreen mode Exit fullscreen mode

You can then later unwrap and check the underlying error using errors.Is and errors.As.

6. Checking Errors:

The errors package provides utilities to work with errors.

errors.Is: Check if an error is a specific error or wrapped version of it.
errors.As: Check if an error matches a certain type and, if so, get the typed value.

var specificErr MyError
if errors.As(err, &specificErr) {
    // handle specificErr
}
Enter fullscreen mode Exit fullscreen mode

Best Practices:

  • Always handle errors explicitly. Don't ignore them unless you're certain it's safe to do so.
  • When wrapping errors, provide context so that the root cause can be traced.
  • Avoid having too many error return values. If a function returns multiple errors, consider if there's a way to simplify or break up the function.
  • Use custom error types judiciously. Often, a simple error string is sufficient.

By following these practices and understanding Go's unique approach to error handling, you can write more resilient and maintainable code.

Top comments (0)