Go Language Error Handling
Internal
TODO
- Second pass needed.
- Go had no exceptions, what is the idiomatic way to handle errors? Handling errors after each function call seems noisy.
- https://gabrieltanner.org/blog/golang-error-handling-definitive-guide
- https://golang.org/ref/spec#Handling_panics
Overview
Errors in Go are instances that implement the interface error
, one of the Go pre-declared type identifiers. The error
interface is the conventional way of representing an error condition, with the nil
value representing no error:
type error interface {
Error() string
}
The Error()
method prints the error message.
The errors
package contains functions for handling errors: creating new instances with New()
, checking for wrapped errors with Is()
, etc.
Keep errors descriptive, yet compact. It should be easy to understand what exactly went wrong by reading the error message.
Declaration
Initialization
err := errors.New("something went wrong")
Note that New()
returns a pointer.
Another way to create errors is with fmt.Errorf()
:
err := fmt.Errorf("something")
fmt.Errorf()
can also be used to wrap errors.
Idiomatic Error Handling
A common error handling pattern in Go involve functions returning several "good" values and an error value, as the last value in the series.
For valid invocations, the error return value is nil
.
If the invocation caused an error, the error return value is not nil
. If an error occurs the other values being returned by the function should be ignored.
Explicit error handling is some times cited as one of the strong points of Go.
f, err := os.Open("...")
if err != nil {
fmt.Println(err) // will call the Error() method
return
}
f, err := os.Open("...")
if err != nil {
return errors.New("something went wrong")
}
var somethingWentWrong = errors.New("something went wrong")
f, err := os.Open("...")
if err != nil {
return somethingWentWrong
}
There's an if
syntax that supports this error handling idiom:
if result, err := f(); err {
// handle error
...
} else {
// handle success
result ...
}
Use panics in truly exceptional cases.
Do not discard errors by using the blank identifier _
assignment, aways handle each error.
When calling a function returning an error, always handle the error first.
Error strings should start with a lowercase character, unless they begin with names that require capitalization.
Error strings, unlike comments, should not end with punctuation marks.
Wrapping Errors
Define a wrapped error. Compare fmt.Errorf()
to errors.Unwrap()
.
Existing errors can be wrapped in outer errors, which may add information relevant to the context that caught the error being processed. However, wrapping error is more than concatenating strings.
The conventional way of wrapping errors in Go is to use the %w
formatting clause with fmt.Errorf()
:
if err != nil {
return fmt.Errorf("additional information: %w", err)
}
The wrapped error message, assuming that the original error is "initial information", with be:
additional information: initial information
Checking for Wrapped Errors
A wrapped error can be identified in an enclosing error with the errors.Is(<outer_error>, <sought_for_error>)
function:
var BlueError = errors.New("some information")
var GreenError = errors.New("some information")
...
// wrap the error in an outer error
outerError := fmt.Errorf("addtional info: %w", BlueError)
if errors.Is(outerError, BlueError) {
fmt.Println("found blue error")
}
if errors.Is(outerError, GreenError) {
fmt.Println("found green error")
}
BlueError
is correctly identified, even though both BlueError
and GreenError
carry the same string. How?
errors.As()
TODO
Panics
A panic indicates a programmer error (attempting to access an array index out of bounds, writing an uninitialized map, etc.). Panics can also be triggered with:
panic(<message>)
The panic can be caught on the stack outside the function that caused the panic, by executing recover()
. Recover can be called in any upper function on the stack, or even if the function that triggers the panic, if the invocation is made with defer
.
The recover()
built-in function allows a program to manage behavior of a panicking goroutine. Executing a call to recover inside a deferred function, but not any function called by it, stops the panicking sequence by restoring normal execution and retrieves the error value passed to the call of panic. If recover is called outside the deferred function it will not stop a panicking sequence. In this case, or when the goroutine is not panicking, or if the argument supplied to panic was nil
, recover returns nil
. Thus the return value from recover reports whether the goroutine is panicking.
Error Handling Idioms
The fact that Go function can return multiple value may be used in implementing a mechanism where a function returns the "good" value and an error value, instead of an exception. If an error occurs (the error instance is not nil), you should never trust the other values being returned by the function, they should always be ignored. This is an idiomatic way of handling errors returned by functions. For a callee function A and a calling function B:
// the "callee" func A(...) (<business-result-type>, error) { var result <business-result-type> // processing ... // some error occurs return <zero-value-for-type>, errors.New("dang") // all good, make sure to return all values, including a nil error return result, nil } // the calling function func B(...) (<business-result-type>, error) { result, err := A(...) if err != nil { return <zero-value-for-type>, err } // process the callee's returned result ... // all good, make sure to return all values, including a nil error return result, nil }
The fact that functions return error values can be used in conjunction with the special if syntax:
if value, error := f(); err { // handle err } else { // handle success }
More about multiple return values can be found here: