Custom Error Objects in Golang

In Go, the default error type is an interface that can only contain a string. However, for more structured error handling, you can create custom error objects that include additional information like status codes or nested errors.

The Error Interface

Go’s error is an interface type:

type error interface {
    Error() string
}

Any type that implements the Error() string method satisfies the error interface. The fmt package calls this method to print errors.

Implementing a String() Method

You can create a custom error type with a String() method for basic formatting:

package main

import (
    "errors"
    "fmt"
)

// CusErr is a custom error object
type CusErr struct {
    Status int
    Err    error
}

func (cusErr *CusErr) String() string {
    return fmt.Sprintf("%s ,%d", cusErr.Err, cusErr.Status)
}

func main() {
    cus := &CusErr{10, errors.New("abc error")}
    fmt.Println(cus)

    cus2 := testErr()
    fmt.Println(cus2)
}

// testErr returns a custom error
func testErr() *CusErr {
    return &CusErr{1, errors.New("test error")}
}

Output:

abc error ,10
test error ,1

Implementing the Error() Method to Satisfy the Error Interface

To make your custom type satisfy the error interface, implement the Error() method:

package main

import (
    "errors"
    "fmt"
)

// CusErr is a custom error object
type CusErr struct {
    Status int
    Err    error
}

func (cusErr *CusErr) Error() string {
    return fmt.Sprintf("msg: %s ,code: %d", cusErr.Err, cusErr.Status)
}

func main() {
    cus := &CusErr{10, errors.New("abc error")}
    fmt.Println(cus)

    cus1 := testErr1()
    fmt.Println(cus1)

    cus2 := testErr2()
    fmt.Println(cus2)
    
    if cuserr, ok := cus2.(*CusErr); ok {
        fmt.Println("status:", cuserr.Status)
    }
}

func testErr1() *CusErr {
    return &CusErr{1, errors.New("test error")}
}

func testErr2() error {
    return &CusErr{2, errors.New("test error")}
}

Output:

msg: abc error ,code: 10
msg: test error ,code: 1
msg: test error ,code: 2
status: 2

Best Practices

  • Type Assertion: Use type assertion to check and access custom error fields, as shown in the example.
  • Wrapping Errors: Go 1.13 introduced fmt.Errorf with %w for wrapping errors, allowing errors.Is and errors.As for better error handling.
  • Avoid Over-Engineering: Only create custom errors when you need additional context beyond a simple string.

Conclusion

Custom error objects allow for richer error information and better debugging. By implementing the Error() method, your types can seamlessly integrate with Go’s error handling ecosystem.

Resources