Compared with other mainstream languages such as Javascript, Java and Python, Golang's error handling may be different from those you are familiar with. That's why I got this idea. Let's talk about golang's error handling methods and how to handle errors in actual development. Because Golang has a basic understanding of developers in the face of sharing, I won’t go into details on some simple places.
How to define errors
In the golang language, whether in type checking or compilation, errors are treated as values, and they are not different from types such as string or integer. There is no difference between declaring a string type variable and declaring an error type variable.
You can define an interface as the type of error. What kind of information an error can provide is determined by yourself. This is the advantage of error as a value in golang, but it is naturally disadvantageous to doing so. The definition of error is determined by its definition developers, that is, the inclusion of too many artificial subjective factors in error.
package main import ( "fmt" "io/ioutil" ) func main(){ dir, err := ("","temp") if err != nil{ ("failed to create temp dir: %v",err) } }
The key position of errors in language
Error handling design in Go language has always been something that everyone likes to discuss. Error handling is the core of the language, but the language does not specify how to deal with errors. The community has made efforts to improve and regulate error handling, but many have overlooked the centrality of errors in our application field. That is, errors are as important as customers and order types.
Error in Golang
An error indicates that an unwanted situation occurred in the application. For example, you want to create a temporary directory where you can store some files for the application, but the creation of this directory fails. This is an undesirable situation, and it can be expressed as an error.
Creating custom errors allows richer error information to be passed to the caller. The return value returns the error to the caller to handle the error. Golang itself allows functions to have multiple return values, so errors are usually returned to the caller as the last parameter of the function.
errors are I/O
- Sometimes the developer is the producer of error (write error)
- Sometimes developers are consumers of error again (read error)
That is, part of our development program is to read and write error
The context of errors
What is the context of an error? Some factors need to be considered in how to define an error. For example, in different programs, we define errors and handle errors in the same way.
- CLI Tools
- Library
- Long-running system
And we need to consider the people who use the program, what methods do they use the system, these factors are factors that we need to consider when designing and defining error messages.
The wrong type
As for the core of the error, then the error may be an error that we expected, and the error may also be that we did not consider, such as invalid memory, arrays cross boundaries, which means that the code itself cannot solve temporarily. Such errors often make the code panic, so Panic. Usually such errors are catastrophic for the program and cannot be fixed.
Custom errors
As mentioned earlier, errors are represented by built-in error interface types, which are defined as follows.
type error interface { Error() string }
Here are 2 examples to define error, and define two structs that implement the Error() interface.
type SyntaxError struct { Line int Col int } func (e *SyntaxError) Error() string { return ("%d:%d: syntax error", , ) }
type InternalError struct { Path string } func (e *InternalError) Error() string { return ("parse %v: internal error", ) }
The interface contains a method Error() that returns an error message as a string. Each type that implements the wrong interface can be used as an error. When printing errors using methods such as , Golang will automatically call the Error() method.
In Golang, there are multiple ways to create custom error messages, each with its own advantages and disadvantages.
String-based error
String-based errors can be customized using two out-of-the-box methods in Golang. Which are relatively simple errors that only return description error information.
err := ("math: divided by zero")
Passing error message into () method can be used to create a new error
err2 := ("math: %g cannot be divided by zero", x)
Through string format, the error message can be included in your error message. That is, some formatting functions are added to the error message.
Error in custom data structure
You can create a custom error type by implementing the Error() function defined in the Error interface on your structure. Here is an example.
Defer, panic and recover
Go does not have exceptions like many other programming languages, including Java and Javascript, but has a similar mechanism, namely "Defer, panic and recover". However, the usage of panic and recover is very different from exceptions in other programming languages, because the code itself cannot cope with and is irrecoverable.
Defer
It's a bit similar to the destructor. After the function is executed, it does some final work such as resource release. The advantage is that its execution has nothing to do with its position in the code, so you can write it behind your reading and writing resource statements to avoid forgetting to do some resource release work. About defer output is also a question that interviewers like to ask during interviews.
package main import( "fmt" "os" ) func main(){ f := createFile("tmp/") defer closeFile(f) writeFile(f) } func createFile(p string) * { ("creating") f, err := (p) if err != nil{ panic(err) } return f } func closeFile(f *){ ("closing") err := () if err != nil{ (, "error:%v\n",err) (1) } } func writeFile(f *){ ("writing") (f,"machine leanring") }
The defer statement pushes the function into a stack structure. At the same time, the functions in the stack structure will be called after the return statement is executed.
package main import "fmt" func main(){ // defer ("word") // ("hello") ("hello") for i := 0; i <=3; i++ { defer (i) } ("world") }
hello
world
3
2
1
0
You can implement a custom error type by implementing the Error() function defined in the Error interface on your structure. Here is an example.
Panic
The panic statement sends a signal to Golang. At this time, the code usually cannot solve the current problem, so the normal execution process of the code is stopped. Once panic is called, all delay functions are executed and the program crashes, with log information including panic values (usually error messages) and stack traces.
For example, when a number is divided by 0, Golang will appear panic.
package main import "fmt" func main(){ divide(5) } func divide(x int){ ("divide(%d)\n",x+0/x) divide(x-1) }
divide(5) divide(4) divide(3) divide(2) divide(1) panic: runtime error: integer divide by zero goroutine 1 [running]: (0x0) /Users/zidea2020/Desktop/mysite/go_tut/:10 +0xdb (0x1) /Users/zidea2020/Desktop/mysite/go_tut/:11 +0xcc (0x2) /Users/zidea2020/Desktop/mysite/go_tut/:11 +0xcc (0x3) /Users/zidea2020/Desktop/mysite/go_tut/:11 +0xcc (0x4) /Users/zidea2020/Desktop/mysite/go_tut/:11 +0xcc (0x5) /Users/zidea2020/Desktop/mysite/go_tut/:11 +0xcc () /Users/zidea2020/Desktop/mysite/go_tut/:6 +0x2a exit status 2
Recover
Go language provides built-in recovery functions. As mentioned earlier, once the panic is panic, the logic will go to the defer, so we wait in the defer, and calling the recover function will capture the current panic, and the captured panic will not be passed upward. The recovery will then end the current Panic state and return the error value of Panic.
package main import "fmt" func main(){ accessSlice([]int{1,2,5,6,7,8}, 0) } func accessSlice(slice []int, index int) { defer func() { if p := recover(); p != nil { ("internal error: %v", p) } }() ("item %d, value %d \n", index, slice[index]) defer ("defer %d \n", index) accessSlice(slice, index+1) }
Packaging error
Golang also allows wrapping errors, and by nesting errors, adding an extra information to the original error message to help the caller judge the problem and how to deal with the information in the future. Provides some specific information to save the original error by using the %w flag and the function, as shown in the following example.
package main import ( "errors" "fmt" "os" ) func main() { err := openFile("non-existing") if err != nil { ("error running program: %s \n", ()) } } func openFile(filename string) error { if _, err := (filename); err != nil { return ("error opening %s: %w", filename, err) } return nil }
The above has demonstrated how to wrap an error through code. The program will print out the wrapped error using the file name added, and also print the original error message passed to the %w flag. Here we add a function that also provides Golang, which can restore error information by using it to obtain the original error information.
package main import ( "errors" "fmt" "os" ) func main() { err := openFile("non-existing") if err != nil { ("error running program: %s \n", ()) // Unwrap error unwrappedErr := (err) ("unwrapped error: %v \n", unwrappedErr) } } func openFile(filename string) error { if _, err := (filename); err != nil { return ("error opening %s: %w", filename, err) } return nil }
Incorrect type conversion
Sometimes it is necessary to convert between different error types. In some cases, it is necessary to add information to the error through type conversion, or change the expression method. Functions provide a simple and safe way to convert output by finding matching error types in the error chain. If no match is found, the function returns false.
package main import ( "errors" "fmt" "io/fs" "os" ) func main(){ // Casting error if _, err := ("non-existing"); err != nil { var pathError * if (err, &pathError) { ("Failed at path:", ) } else { (err) } } }
Here, attempts are made to convert the general error type to , so that that specific error information can be accessed, which is stored on the Path property in the structure.
Error type check
Golang provides a function to check whether the error type is the specified error type. The function returns a Boolean value to indicate whether the error type is the specified error type.
package main import ( "errors" "fmt" "io/fs" "os" ) func main(){ // Check if error is a specific type if _, err := ("non-existing"); err != nil { if (err, ) { ("file does not exist") } else { (err) } } }
This is the end of this article about the error handling mechanism in the golang language. For more related golang error handling content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!