01 What is Error
In Go, error is a built-in data type, defined as an interface, defined as follows:
// The error built-in interface type is the conventional interface for // representing an error condition, with the nil value representing no error. type error interface { Error() string }
From this we can see that the interface has only one Error function that returns a string. As long as all types implement this function, an error type is created.
02 How to create an error
The ways to create an error include customizing the type of the error interface implemented, etc.
2.1 Create through methods
Errors created by this method are generally predictable errors. Simply put, the caller can clearly know where the error occurred through the error message, without adding additional context information. We will explain in detail in the following example.
err := ("this is error")
When we look at the implementation of the New method, we can see that it actually returns an errorString structure, which contains a string property and implements the Error method. The code is as follows:
func New(text string) error { return &errorString{text} } // errorString is a trivial implementation of error. type errorString struct { s string } func (e *errorString) Error() string { return }
Use scenario 1
The scene where local variables or anonymous variables are created through functions, and the value or type judgment is not processed in the calling function, and only error logs are printed or recorded.
Example 1
The following code is excerpted from the part that parses PostForm in source code /src/net/http/. When the Body in the request is nil, the error message returned is "missing form body". This information clearly states that the error is caused by the empty request body, so no additional context information is required.
func parsePostForm(r *Request) (vs , err error) { if == nil { err = ("missing form body") return } ct := ("Content-Type") // The subsequent code has been omitted... return }
Example 2
The following code is excerpted from the source code /src/net/http/. The error returned when the URL address in the request body is nil: "http: nil ", indicating that the URL field in the request is nil. And the error returned when the header is nil: "http:nil", indicating that the Header field in the request body is nil.
func (t *Transport) roundTrip(req *Request) (*Response, error) { () ctx := () trace := (ctx) if == nil { () return nil, ("http: nil ") } if == nil { () return nil, ("http: nil ") } //Omit the following code...}
Use scenario 2
Assign the created error to a global variable, we call this variableSentinel error, the sentry error variable can be compared with values using == or , when it is processed.
Example of usage
The sentinel error variable EOF defined in source code /src/io/ represents the end of the file.
var EOF = ("EOF")
In the beego project, there is such an application in the beego/core/utils/ file. When reading the file, the error encountered is not an error at the end of the file will be returned directly. If the error at the end of the file is encountered, the for loop will be interrupted, indicating that the file has read all the contents in the file. as follows:
func GrepFile(patten string, filename string) (lines []string, err error) { //Omit the previous code... fd, err := (filename) if err != nil { return } reader := (fd) for { byteLine, isPrefix, er := () if er != nil && er != { return nil, er } if er == { break } //Omit the following code... }
2.2 Create through methods
There are also two forms of this method, one is with a %w placeholder, and the other is without a %w placeholder.
Use scenario 1: No %w placeholder
When creating an error, the error cannot be described by the created string information, but more context information needs to be added through placeholders, that is, dynamic information.
Example of usage: Without %w placeholder
The following code is excerpted from some codes of gorm/schema/. When the foreign key is not legal, an error with a specific foreign key is returned through ("invalid foreign key: %s", foreignKey). Because the foreign key value can only be determined at runtime. The code is as follows:
func (schema *Schema) buildMany2ManyRelation(relation *Relationship, field *Field, many2many string) { //... if len() > 0 { ownForeignFields = []*Field{} for _, foreignKey := range { if field := (foreignKey); field != nil { ownForeignFields = append(ownForeignFields, field) } else { = ("invalid foreign key: %s", foreignKey) return } } } //... }
Use scenario 2: With %w placeholder
In some scenarios, the caller needs to know the original error message, and at this time, he needs to use the method with the %w placeholder to create an error. Using this method actually forms an error chain.
The usage is as follows:
filename := "" ("%w:%s", ("unsupported extension"), filename)
Let's look at the source code again:
func Errorf(format string, a ...interface{}) error { p := newPrinter() = true (format, a) s := string() var err error if == nil { err = (s) } else { err = &wrapError{s, } } () return err }
From the source code, we can see that if the %w placeholder is included, a wrapError structure type value is created. Let's look at the definition of the wrapError structure:
type wrapError struct { msg string err error }
The field err is the original error, and msg is the formatted error message.
Example of usage: with %w placeholder
Suppose we have a function that querys the contract from the database. When querying the record from the database to be empty, an error will be returned. We use the %w placeholder to wrap the error and return it to the caller.
const query = "..." func (s Store) GetContract(name string) (Contract, error) { id := getID(name) rows, err := (query, id) if err != nil { if err == { return Contract{}, ("no contract found for %s: %w", name, err) } // ... } // ... }
OK, now the caller of GetContract can know the original error message. In the caller logic, we can use it to determine whether err contains a value. Let's look at the caller's code:
contract, err := ("Raul Endymion") if err != nil { if (err, ) { // Do something specific } }
2.3 Customize the structure that implements the error interface
Use scenarios
This is relatively speaking and applies to the definition of predictable errors. And when an unpredictable error occurs, you need to customize the error type.
Example of usage
Let’s take the source code in the /src/io/fs/ file in go as an example to see which elements you need to include in your custom error type.
// PathError records an error and the operation and file path that caused it. type PathError struct { Op string Path string Err error } func (e *PathError) Error() string { return + " " + + ": " + () } func (e *PathError) Unwrap() error { return }
First, look at the structure. There is an Err of the error interface type, which represents the error source. Because according to the above explanation, when the error is passed layer by layer to the caller, we need to track the original error information of each layer, so this field needs to wrap the error to form an error chain. In addition, there are two fields Op and Path, which represent the operation and the path of the operation that generated the error. These two fields are called unpredictable errors: Unsure what error was done for which path that caused the error.
Let's look at the application of this error type in the code.
Application 1:In the code in go file src/embed/, when reading a directory, an error of type PathError is returned, which means that when reading the directory operation, it is a directory, so the file content cannot be read directly.
func (d *openDir) Read([]byte) (int, error) { return 0, &{Op: "read", Path: , Err: ("is a directory")} }
Application 2:In the code in go file src/embed/, there is a function for reading the file. When offset is less than 0, a PathError is returned, which means that the parameters are incorrect when reading the file.
func (f *openFile) Read(b []byte) (int, error) { if >= int64(len()) { return 0, } if < 0 { return 0, &{Op: "read", Path: , Err: } } n := copy(b, [:]) += int64(n) return n, nil }
The definition is as follows
ErrInvalid = ("invalid argument")
It can be seen that the values of the three field in PathError are unpredictable and can only be determined specifically when the program is running. Therefore, in this scenario, you need to customize the error type.
In addition, we also noticed that the Unwrap function is implemented in this custom type. This function is mainly for coordination and use, because these two functions are unpacked and compared one by one when used.
03 and
According to the previous section we have obtained that the error can be organized into an error chain through the %w placeholder. Let's take a look at the passing and how to deal with the wrapped error chain.
The function is to determine whether there are errors in the error chain that are equal to the specified error value, which is equivalent to the == operator. Note that here is a specific error value, like ErrRecordNotFound defined in gorm:
var ErrRecordNotFound = ("record not found")
Then we can use it like this:
(err, ErrRecordNotFound)
Function, this function is used to check whether the error in the error chain has a specified error type.
The following code example is excerpted from some code in the etcd project etcd/server/embed/config_logging.go. It represents whether there is an error in the err chain that can be regarded as a type. If so, the error value in err will be assigned to the syntaxError variable. The code is as follows:
// setupLogRotation initializes log rotation for a single file path target. func setupLogRotation(logOutputs []string, logRotateConfigJSON string) error { //... if err := ([]byte(logRotateConfigJSON), &logRotationConfig); err != nil { var unmarshalTypeError * var syntaxError * switch { case (err, &syntaxError): return ("improperly formatted log rotation config: %w", err) case (err, &unmarshalTypeError): return ("invalid log rotation config: %w", err) } } ("rotate", func(u *) (, error) { = [1:] return &logRotationConfig, nil }) return nil }
Summarize
This article explains the practical application scenarios of various ways to create errors from the perspective of application scenarios. The code in the example should be selected from golang source code or open source project as much as possible. At the same time, each application scenario is not absolute and requires flexible application. I hope this article will be helpful to you in actual use.
The above is a detailed summary and application of the method of creating errors in Golang. For more information about creating errors in Golang, please follow my other related articles!