SoFunction
Updated on 2025-03-05

Summary of how to create custom errors in Go

introduction

Go provides two ways to create errors in the standard library, [and], Sometimes these two mechanisms are not sufficient to adequately capture and report what happens when communicating more complex error messages with users, or when communicating with future self during debugging. In order to convey more complex error messages and implement more functions, we can implement standard library interface types.error

The syntax is as follows:

type error interface {
  Error() string
}

builtinPackageerrorDefined as an interface, it has only oneError()Method, return an error message in the form of a string. By implementing this method, we can convert any type defined to our own error.

Let's try running the following example to seeerrorImplementation of interface:

package main

import (
	"fmt"
	"os"
)

type MyError struct{}

func (m *MyError) Error() string {
	return "boom"
}

func sayHello() (string, error) {
	return "", &MyError{}
}

func main() {
	s, err := sayHello()
	if err != nil {
		("unexpected error: err:", err)
		(1)
	}
	("The string:", s)
}
Outputunexpected error: err: boom
exit status 1

Here we create a new empty structure typeMyError, and defined on itError()method.Error()Method returns string"boom"

existmain()In, we call the functionsayHello, the function returns an empty string and a new oneMyErrorExample. becausesayHelloThere will always be an error, somain()if statement inThe call will always be executed. Then, we usePrint a short prefix string"unexpected error:"as well aserrSaved in variablesMyErrorExample.

Please note that we don't have to call it directlyError(),becausefmtThe package can automatically detect thisError Implementation of . It calls transparentlyError()To get the string"boom"and set it with the prefix string"unexpected Error: err:"Connect.

Collect details in custom errors

Sometimes, custom errors are the easiest way to catch detailed error information. For example, suppose we want to capture the status code of the error generated by the HTTP request; run the following program to viewerrorImplementation, which allows us to clearly capture information:

package main

import (
	"errors"
	"fmt"
	"os"
)

type RequestError struct {
	StatusCode int

	Err error
}

func (r *RequestError) Error() string {
	return ("status %d: err %v", , )
}

func doRequest() error {
	return &RequestError{
		StatusCode: 503,
		Err:        ("unavailable"),
	}
}

func main() {
	err := doRequest()
	if err != nil {
		(err)
		(1)
	}
	("success!")
}

Outputstatus 503: err unavailable
exit status 1

In this example, we create a new oneRequestErrorExamples and use theThe function provides the status code and an error. Then, like the previous example, we usePrint it.

existRequestErrorofError()In the method, we useFunctions use the information provided when creating an error to construct a string.

Type assertions and custom errors

errorThe interface only exposes one method, but we may need to access iterrorOther ways to correctly handle errors. For example, we may have several temporary oneserrorCustom implementation, can be achieved through existing temporary()Methods are searched.

The interface provides a wider collection of methods for types, so we have to use type assertions to change the method the view is showing, or delete it altogether.

The following example was in the previousRequestErrorAdded aTemporary()Method, which will indicate whether the caller should retry the request:

package main

import (
	"errors"
	"fmt"
	"net/http"
	"os"
)

type RequestError struct {
	StatusCode int

	Err error
}

func (r *RequestError) Error() string {
	return ()
}

func (r *RequestError) Temporary() bool {
	return  ==  // 503
}

func doRequest() error {
	return &RequestError{
		StatusCode: 503,
		Err:        ("unavailable"),
	}
}

func main() {
	err := doRequest()
	if err != nil {
		(err)
		re, ok := err.(*RequestError)
		if ok {
			if () {
				("This request can be tried again")
			} else {
				("This request cannot be tried again")
			}
		}
		(1)
	}

	("success!")
}

Outputunavailable
This request can be tried again
exit status 1

existmain()In, we calldoRequest(), it returns aerrorThe interface is given to us. Let's print first error()The error message returned by the method. Next, we try to use type assertionsre, ok := err.(*RequestError)Come to exposeRequestErrorAll methods in . If the type assertion is successful, then we useTemporary()Method to see if this error is a temporary error. becausedoRequest()SetStatusCodeyes503, this is withmatch, so returntrueand leads to printing" this request can be try again". In practice, we send another request instead of printing a message.

Packaging error

Usually, errors will occur outside the program, such as: databases, network connections, etc. The error messages provided by these errors cannot help anyone find the source of the error. Wrapping the error with extra information at the beginning of the error message can provide some necessary context for successful debugging.

The following example demonstrates how we attach some context information to the obscure ones returned from other functionserrorsuperior:

package main

import (
	"errors"
	"fmt"
)

type WrappedError struct {
	Context string
	Err     error
}

func (w *WrappedError) Error() string {
	return ("%s: %v", , )
}

func Wrap(err error, info string) *WrappedError {
	return &WrappedError{
		Context: info,
		Err:     err,
	}
}

func main() {
	err := ("boom!")
	err = Wrap(err, "main")

	(err)
}

Outputmain: boom!

WrappedErrorIt is a structure with two fields: one isstringType of context message, another one iserrortype,WrappedErrorMore information is provided. whenError()When the method is called, we use it againto print the context message, thenError(I know implicit callsError()method).

existmain()We useCreate an error and use thewrapFunction wraps this error. This allows us to point out thiserrorYes"main"generated in . In addition, due to ourWrappedErrorAlso oneerrorWe can pack otherWrappedError, which will allow us to see a chain to help us track the source of the error. With the help of the standard library, we can even embed a full stack trace in the error.

Summarize

becauseerrorThere is only one method for the interface, and we have seen that we can provide different types of errors for different situations. This can cover everything from communicating multiple pieces of information as part of an error to implementing exponential fallback. While the error handling mechanism in Go seems simple on the surface, we can use these custom errors to handle common and uncommon situations, resulting in a fairly rich processing.

Go has another mechanism for communicating unexpected behaviors. In the next article in the error handling series, we will look at Panics – what they are and how to deal with them.

This is the end of this article about creating custom errors in Go. For more related content on creating custom errors in Go, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!