Errors are values.
Go Proverbs
Don’t just check errors, handle them gracefully.
Errors in Go make for an interesting topic with various discussions around them. I appreciate the simplicity, particularly the wrapping mechanism which maintains the original error causing the failure.
Though returning errors is straightforward, their proper handling can be slightly more complex. Here are the key points about errors:
- error is a very simple interface requiring only
Error() stringmethod. - Use custom error types for better distinction between various classes of errors.
- If not using custom type, wrap errors. Always use
%wverb, when wrapping using fmt.Errorf. - If you need to know what exact error type has been returned, use errors.As. It will tell you if error tree contains one that is of specific type.
- If you want to check for specific error details, use errors.Is. It will tell you if error tree contains one that is exactly what you asked (check equality).
Example
package main
import (
"encoding/json"
"errors"
"fmt"
"os"
)
type FileReadError struct {
Err error
Path string
}
func (e FileReadError) Error() string {
return fmt.Sprintf("error while reading file %s: %s", e.Path, e.Err)
}
type FileParseError struct {
Err error
Path string
}
func (e FileParseError) Error() string {
return fmt.Sprintf("error while parsing file %s: %s", e.Path, e.Err)
}
func ReadJson(f string) error {
d, err := os.ReadFile(f)
if err != nil {
return FileReadError{Err: err, Path: f}
}
var v interface{}
err = json.Unmarshal(d, &v)
if err != nil {
return FileParseError{Err: err, Path: f}
}
return nil
}
func check(err error) {
if err == nil {
fmt.Printf("There is no error\n")
} else if errors.As(err, &FileReadError{}) {
fmt.Printf("Could not read file (%s)|\n", err)
} else if errors.As(err, &FileParseError{}) {
fmt.Printf("Could not parse file (%s)|\n", err)
} else {
fmt.Printf("Unknown error (%s) (%T)\n", err, err)
}
}
func main() {
for _, f := range []string{"/nonexistent", "/proc/stat", "valid.json"} {
err := ReadJson(f)
check(err)
}
}
