This is a creation in Article, where the information may have evolved or changed.
When writing the Go language code, we should be accustomed to using the error type value to indicate an abnormal state. As a idiom, many of the functions and methods in the Go Language standard library code package also indicate the error state and its details with the return error type value.
Error is a predefined identifier that represents an interface type built into the go language. The type of this interface is declared as follows:
Type error Interface{error () string}
which the meaning of the error method declaration is to provide details about the current error state for the method caller. Any data type can become an implementation of the error interface type as long as it implements the error method that returns a string type value. In general, however, we do not need to write an implementation type of error ourselves. The standard library code package for the Go language errors provides us with a function new for creating errors type values. The declaration of this method is as follows:
Func New (text string) error {return &errorstring{text}}
Errors. The new function takes a parameter value of type string and can return an error type value. The dynamic type of this error type value is the errors.errorstring type. The unique parameter of the new function is used to initialize the value of that errors.errorstring type. As can be seen from the name representing this implementation type, this type is a package-level private type. It is only part of the internal implementation of the errors package, not the public API. The errors.errorstring types and their methods are declared as follows:
Type errorstring struct { s string}func (e *errorstring) Error () string { return E.S}
Passed to errors. The parameter value of the new function is the result value returned when we call its error method.
We can use the print function in the FMT to print out the details of the error represented by the error type value, like this:
var err error = errors. New ("A normal error.")
These print functions call the error method of the value when it discovers that the printed content is an error type value and use the resulting value as the string representation of the value. So we pass it on to errors. The parameter value of new is the string representation of the value of the error type it returns.
Another way to generate an error type value is to call the Errorf function in the FMT package. The code that calls it is similar to the following:
ERR2: = Fmt. Errorf ("%s\n", "A normal error.")
With FMT. The printf function is the same, FMT. The Errorf function can generate a string type value based on the format specifier and subsequent parameters. But with the FMT. The printf function is different from the FMT. The Errorf function does not print the generated string type value on the standard output, but instead uses it to initialize an error type value and return it to the caller as the result value of the function. In FMT. Inside the Errorf function, the creation and initialization of an error type value is done by calling errors. new function to complete.
In most cases, errors. The new function and the Fmt.errorf function are sufficient to satisfy the requirement that we create the error type value. However, the interface type error gives us a lot of room for expansion. We can define our own error types as needed. For example, we can use additional fields and methods to allow the program consumer to get more error information. For example, struct type OS. Patherror is an implementation type of an error interface type. Its declaration contains 3 fields, which allows us to get more information from the result worth of its error method. Os. The Patherror types and their methods are declared as follows:
Patherror records an error and the operation and file path that caused It.type patherror struct {Op stringpath stri Ngerr Error}func (e *patherror) error () string {return E.op + "" + E.path + ":" + E.err.error ()}
From the OS. We can tell from the declaration of the Patherror type that its 3 fields are public. Therefore, we can access them directly from the selector in any location. However, in general, the type of the related result declaration in a function or method should be an error type, not an implementation type of an error type. This is also to follow the principles of interface-oriented programming. In this case, we often need to first determine the dynamic type of the value of the error type obtained, and then make the necessary type conversions and subsequent operations. For example:
File, ERR3: = OS. Open ("/etc/profile") if ERR3! = nil {if PE, OK: = Err3. ( *os. PATHERROR); OK {fmt. Printf ("Path Error:%s (op=%s, path=%s) \ n", PE. ERR, PE. Op, PE. Path)} else {fmt. Printf ("Unknown Error:%s\n", ERR3)}}
We use a type assertion expression and an if statement to the OS. The Open function returns the value of the error type for processing. This is the same as using the error type value as the result value to express the wrong state of the function execution, which is also one of the idioms for exception handling in the go language.
If the OS. The open function does not have any errors during execution, so we can read the contents of the file represented by a variable file. The relevant code is as follows:
r: = Bufio. Newreader (file) var buf bytes. bufferfor {ByteArray, _, Err4: = R.readline () if err4! = Nil {if err4 = = Io. EOF {break} else {fmt. Printf ("Read Error:%s\n", ERR4) Break}} else {buf. Write (ByteArray)}}
Io. The EOF variable is initialized by the result value of the Errors.new function. EOF is the abbreviation for a file terminator (end of a). For a file read operation, it means that the reader has read to the end of the file. So, strictly speaking, EOF should not be counted as a real error, but only as a "false signal".
The variable r represents a reader. Its ReadLine method returns 3 result values. The type of the third result value is the error type. When the reader reads to the end of the file represented by files, the ReadLine method returns the value of the variable io.eof directly as its third result value. If the result of the judgment is true, then we can directly terminate the execution of the For statement that is used to read the contents of the file continuously. Otherwise, we should realize that there is a real error in the process of reading the contents of the file, and take appropriate measures.
Note that only if the value of a variable of two error types is indeed the same value, the comparison operator = = is used to determine if the values are true. From another point of view, we can pre-declare some variables of the error type and use them as special "error signals". Any function or method that needs to return the same type of "error signal" can be used to directly use such pre-declared values. So we can easily use = = to identify these "error signals" and to do the corresponding operation.
However, it is important to note that the values of such variables must be immutable. That is, the declaration of their actual type should not contain any exposed fields, and the methods attached to those types should not contain statements that assign values to their fields. For example, the OS we mentioned earlier. The Patherror type is not suitable as a dynamic type for the values of such variables, otherwise it is likely to cause unpredictable consequences.
This practice of providing convenience to program users by pre-declaring variables of the error type is common in the Go Language standard library code package.
Another technique for implementing the error interface type is that we can also extend it by embedding the error interface type into a new interface type. For example, the error interface type in standard library code package NET is declared as follows:
An error represents a network Error.type Error interface {errortimeout () bool //Is the error a timeout? Temporary () bool//is the error temporary?}
Some functions declared in the net package return a dynamic type of net. The error type value of error. On the consumer side, the method of determining the dynamic type of the value of the error type is basically the same as mentioned earlier.
If the dynamic type of the variable err is net. Error, then we can determine whether the current error state is temporary based on the result value of its temporary method:
If neterr, OK: = Err. (NET. ERROR); OK && neterr.temporary () {}
If it is temporary, then you can retry the previous operation after a period of time, otherwise log the information of the error state and exit. If we do not type assert this error type value, we will not be able to get the extra property to the current error state, and we cannot decide whether to retry the operation. The benefits of this seamless extension of the error type are obvious.
In the go language, the correct handling of errors is very important. The design of the language itself and the idioms shown in the standard library code encourage us to examine the errors that occur explicitly. While this will make the Go language code look a bit verbose, there are some tricks we can use to simplify them. Most of these techniques are much the same as common programming best practices, or have been or will be included in what we say (custom error types, single duty functions, etc.), so this is not a problem. Moreover, this little cost is much less than the drawbacks of the traditional try-catch approach.