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 }
builtinPackageerror
Defined 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 seeerror
Implementation 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 oneMyError
Example. becausesayHello
There will always be an error, somain()
if statement inThe call will always be executed. Then, we use
Print a short prefix string
"unexpected error:"
as well aserr
Saved in variablesMyError
Example.
Please note that we don't have to call it directlyError()
,becausefmt
The 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 viewerror
Implementation, 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 oneRequestError
Examples and use theThe function provides the status code and an error. Then, like the previous example, we use
Print it.
existRequestError
ofError()
In the method, we useFunctions use the information provided when creating an error to construct a string.
Type assertions and custom errors
error
The interface only exposes one method, but we may need to access iterror
Other ways to correctly handle errors. For example, we may have several temporary oneserror
Custom 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 previousRequestError
Added 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 aerror
The 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 exposeRequestError
All methods in . If the type assertion is successful, then we useTemporary()
Method to see if this error is a temporary error. becausedoRequest()
SetStatusCode
yes503
, this is withmatch, so return
true
and 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 functionserror
superior:
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!
WrappedError
It is a structure with two fields: one isstring
Type of context message, another one iserror
type,WrappedError
More information is provided. whenError()
When the method is called, we use it againto print the context message, then
Error
(I know implicit calls
Error()
method).
existmain()
We useCreate an error and use the
wrap
Function wraps this error. This allows us to point out thiserror
Yes"main"
generated in . In addition, due to ourWrappedError
Also oneerror
We 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
becauseerror
There 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!