Golang Error Wrapping
This document summarizes Golang error wrapping techniques.
1. Golang Error Wrapping
| |
$ go run main.go
error: outerErrorGolang’s error wrapping is literally a technique of wrapping errors with other errors. Through error wrapping, errors returned by internal functions can also be identified in external functions. [Code 1] and [shell 1] show a common way of handling errors in Golang without error wrapping. When the main() function calls the outerFunc() function, outerFunc(), middleFunc(), and innerFunc() functions are called in order, and since the innerFunc() function returns an error, the outerFunc() function also returns an error.
The problem is that the main() function can only check the outerErr error returned by the outerFunc() function, and cannot check the content of errors returned by the middleFunc() or innerFunc() functions. The most obvious way to solve this problem is to make the external function’s error also differ depending on the error returned by the internal function. The problem is that implementing it this way makes the error handling part of the external function complex. This problem can be easily solved through error wrapping techniques.
1.1. with Standard errors Package
| |
$ go run main.go
--- Unwrap ---
unwrap x 0: outerError: middleError: innerError
unwrap x 1: middleError: innerError
unwrap x 2: innerError
--- Is ---
innerError true
myError false
--- As ---
innerError true: innerMsg
myError falseFrom Golang 1.13 onwards, error wrapping is possible through the fmt.Errorf() function, and wrapped errors can be retrieved again through the errors.Unwrap() function. Also, comparison of wrapped errors is possible through the errors.Is() function, and assertion of wrapped errors is possible through the errors.As() function. Here, both the fmt package and errors package are standard packages of Golang. [Code 2] and [shell 2] show examples of using error wrapping using standard packages.
- Lines 37, 44: Error wrapping is performed through the
fmt.Errorf()function. In this case, error wrapping must be performed through the%wsyntax. You can see thatinnerErrorerror is wrapped twice through themiddleFunc()andouterFunc()functions. - Lines 54~57: Wrapped errors are unwrapped one by one using the
errors.Unwrap()function and output. - Lines 60~70: Wrapped errors are compared using the
errors.Is()function. Since theinnerErrorerror exists inside the error of theouterFunc()function, the result of line 61 becomes True. On the other hand, since themyErrorerror does not exist inside the error of theouterFunc()function, the result of line 66 becomes False. - Lines 73~85: Assertion is performed on the
outerErrerror using theerrors.As()function. Since theinnerErrorerror exists inside the error of theouterFunc()function, the result of line 75 becomes True. On the other hand, since themyErrorerror does not exist inside the error of theouterFunc()function, the result of line 78 becomes False.
1.2. with github.com/pkg/errors Package
| |
$ go run main.go
--- Cause ---
cause: innerError
--- Stack Trace ---
innerError
middleError
main.middleFunc
/root/test/go_profile_http/main.go:38
main.outerFunc
/root/test/go_profile_http/main.go:44
main.main
/root/test/go_profile_http/main.go:52
runtime.main
/usr/local/go/src/runtime/proc.go:250
runtime.goexit
/usr/local/go/src/runtime/asm_amd64.s:1571
outerError
main.outerFunc
/root/test/go_profile_http/main.go:45
main.main
/root/test/go_profile_http/main.go:52
runtime.main
/usr/local/go/src/runtime/proc.go:250
runtime.goexit
/usr/local/go/src/runtime/asm_amd64.s:1571
--- Is ---
innerError true
myError false
--- As ---
innerError true: innerMsg
myError falseWhen using standard packages for error wrapping in Golang, the disadvantage is that it is difficult to identify where in the code the error occurred. This disadvantage can be overcome by using the github.com/pkg/errors package. [Code 3] and [shell 3] show examples of using error wrapping using the github.com/pkg/errors package. There is not much difference in usability from standard packages, but there are slight differences. When using the github.com/pkg/errors package, stack trace output is possible, so it is easy to identify where in the code the error occurred.
- Lines 38, 45: Wrapping is performed through the
github.com/pkg/errors.Wrap()function. There is no need to use thefmtpackage for wrapping. - Line 56: The
github.com/pkg/errorspackage does not provide a function to unwrap wrapped errors one by one. Instead, it provides aCause()function that returns the error that exists innermost. Note that thegithub.com/pkg/errors.Unwrap()function exists, but it is a function that unwraps errors wrapped using thefmtpackage, not errors wrapped throughgithub.com/pkg/errors.Wrap(). - Line 60: When outputting wrapped errors using the
fmt.Printf()function along with the%+vsyntax, the stack trace is also output.
2. References
- Golang Error Handling Demystified : https://adrianlarion.com/golang-error-handling-demystified-errors-is-errors-as-errors-unwrap-custom-errors-and-more/
- Effective Error Handling in Golang : https://earthly.dev/blog/golang-errors/
- Handle errors in Go with errors.Is() and errors.As() : https://gosamples.dev/check-error-type/
- How to compare Go errors : https://stackoverflow.com/questions/39121172/how-to-compare-go-errors
- Golang Error Stack Trace and Logging : https://www.popit.kr/golang-error-stack-trace%EC%99%80-%EB%A1%9C%EA%B9%85/
- [Golang] Error Handling : https://dev-yakuza.posstree.com/ko/golang/error-handling/
- Unwrap doesn’t return the base error - pkg/errors Issue #223 : https://github.com/pkg/errors/issues/223