aboutsummaryrefslogtreecommitdiff
path: root/content/go1.13-errors.article
diff options
context:
space:
mode:
authorRuss Cox <rsc@golang.org>2020-03-09 23:19:59 -0400
committerRuss Cox <rsc@golang.org>2020-03-11 14:10:15 +0000
commit9dd3d9b97af3dba2bd18f1a5e18bd8e8edf78962 (patch)
tree700ba48e63519a9b9bfcfef1aa783ca7e1f821a3 /content/go1.13-errors.article
parent482079d678d84e207dd9ae63266c4bd4e653886b (diff)
content: use tabs consistently for code blocks + indentation
A few articles used four spaces instead. The present format will convert to four spaces for indentation on its own; use tabs. The present format does not care what indentation is used, but use tabs everywhere for consistency. For golang/go#33955. Change-Id: I2bab8aa72fa2f68d48fb833b7317f87d7624a05f Reviewed-on: https://go-review.googlesource.com/c/blog/+/222840 Reviewed-by: Ian Lance Taylor <iant@golang.org>
Diffstat (limited to 'content/go1.13-errors.article')
-rw-r--r--content/go1.13-errors.article228
1 files changed, 114 insertions, 114 deletions
diff --git a/content/go1.13-errors.article b/content/go1.13-errors.article
index fd3fc4c..8c8c029 100644
--- a/content/go1.13-errors.article
+++ b/content/go1.13-errors.article
@@ -14,12 +14,12 @@ allows Go programmers to add whatever information they desire. All it requires
is a type that implements an `Error` method:
- type QueryError struct {
- Query string
- Err error
- }
+ type QueryError struct {
+ Query string
+ Err error
+ }
- func (e *QueryError) Error() string { return e.Query + ": " + e.Err.Error() }
+ func (e *QueryError) Error() string { return e.Query + ": " + e.Err.Error() }
Error types like this one are ubiquitous, and the information they store varies
widely, from timestamps to filenames to server addresses. Often, that
@@ -42,31 +42,31 @@ Go errors are values. Programs make decisions based on those values in a few
ways. The most common is to compare an error to `nil` to see if an operation
failed.
- if err != nil {
- // something went wrong
- }
+ if err != nil {
+ // something went wrong
+ }
Sometimes we compare an error to a known _sentinel_ value, to see if a specific error has occurred.
- var ErrNotFound = errors.New("not found")
+ var ErrNotFound = errors.New("not found")
- if err == ErrNotFound {
- // something wasn't found
- }
+ if err == ErrNotFound {
+ // something wasn't found
+ }
An error value may be of any type which satisfies the language-defined `error`
interface. A program can use a type assertion or type switch to view an error
value as a more specific type.
- type NotFoundError struct {
- Name string
- }
+ type NotFoundError struct {
+ Name string
+ }
- func (e *NotFoundError) Error() string { return e.Name + ": not found" }
+ func (e *NotFoundError) Error() string { return e.Name + ": not found" }
- if e, ok := err.(*NotFoundError); ok {
- // e.Name wasn't found
- }
+ if e, ok := err.(*NotFoundError); ok {
+ // e.Name wasn't found
+ }
** Adding information
@@ -76,9 +76,9 @@ to it, like a brief description of what was happening when the error occurred. A
simple way to do this is to construct a new error that includes the text of the
previous one:
- if err != nil {
- return fmt.Errorf("decompress %v: %v", name, err)
- }
+ if err != nil {
+ return fmt.Errorf("decompress %v: %v", name, err)
+ }
Creating a new error with `fmt.Errorf` discards everything from the original
error except the text. As we saw above with `QueryError`, we may sometimes want
@@ -86,18 +86,18 @@ to define a new error type that contains the underlying error, preserving it for
inspection by code. Here is `QueryError` again:
- type QueryError struct {
- Query string
- Err error
- }
+ type QueryError struct {
+ Query string
+ Err error
+ }
Programs can look inside a `*QueryError` value to make decisions based on the
underlying error. You'll sometimes see this referred to as "unwrapping" the
error.
- if e, ok := err.(*QueryError); ok && e.Err == ErrPermission {
- // query failed because of a permission problem
- }
+ if e, ok := err.(*QueryError); ok && e.Err == ErrPermission {
+ // query failed because of a permission problem
+ }
The `os.PathError` type in the standard library is another example of one error which contains another.
@@ -116,7 +116,7 @@ that you can _unwrap_ `e1` to get `e2`.
Following this convention, we can give the `QueryError` type above an `Unwrap`
method that returns its contained error:
- func (e *QueryError) Unwrap() error { return e.Err }
+ func (e *QueryError) Unwrap() error { return e.Err }
The result of unwrapping an error may itself have an `Unwrap` method; we call
the sequence of errors produced by repeated unwrapping the _error_chain_.
@@ -127,20 +127,20 @@ The Go 1.13 `errors` package includes two new functions for examining errors: `I
The `errors.Is` function compares an error to a value.
- // Similar to:
- // if err == ErrNotFound { … }
- if errors.Is(err, ErrNotFound) {
- // something wasn't found
- }
+ // Similar to:
+ // if err == ErrNotFound { … }
+ if errors.Is(err, ErrNotFound) {
+ // something wasn't found
+ }
The `As` function tests whether an error is a specific type.
- // Similar to:
- // if e, ok := err.(*QueryError); ok { … }
- var e *QueryError
- if errors.As(err, &e) {
- // err is a *QueryError, and e is set to the error's value
- }
+ // Similar to:
+ // if e, ok := err.(*QueryError); ok { … }
+ var e *QueryError
+ if errors.As(err, &e) {
+ // err is a *QueryError, and e is set to the error's value
+ }
In the simplest case, the `errors.Is` function behaves like a comparison to a
sentinel error, and the `errors.As` function behaves like a type assertion. When
@@ -148,15 +148,15 @@ operating on wrapped errors, however, these functions consider all the errors in
a chain. Let's look again at the example from above of unwrapping a `QueryError`
to examine the underlying error:
- if e, ok := err.(*QueryError); ok && e.Err == ErrPermission {
- // query failed because of a permission problem
- }
+ if e, ok := err.(*QueryError); ok && e.Err == ErrPermission {
+ // query failed because of a permission problem
+ }
Using the `errors.Is` function, we can write this as:
- if errors.Is(err, ErrPermission) {
- // err, or some error that it wraps, is a permission problem
- }
+ if errors.Is(err, ErrPermission) {
+ // err, or some error that it wraps, is a permission problem
+ }
The `errors` package also includes a new `Unwrap` function which returns the
@@ -168,25 +168,25 @@ however, since these functions will examine the entire chain in a single call.
As mentioned earlier, it is common to use the `fmt.Errorf` function to add additional information to an error.
- if err != nil {
- return fmt.Errorf("decompress %v: %v", name, err)
- }
+ if err != nil {
+ return fmt.Errorf("decompress %v: %v", name, err)
+ }
In Go 1.13, the `fmt.Errorf` function supports a new `%w` verb. When this verb
is present, the error returned by `fmt.Errorf` will have an `Unwrap` method
returning the argument of `%w`, which must be an error. In all other ways, `%w`
is identical to `%v`.
- if err != nil {
- // Return an error which unwraps to err.
- return fmt.Errorf("decompress %v: %w", name, err)
- }
+ if err != nil {
+ // Return an error which unwraps to err.
+ return fmt.Errorf("decompress %v: %w", name, err)
+ }
Wrapping an error with `%w` makes it available to `errors.Is` and `errors.As`:
- err := fmt.Errorf("access denied: %w", ErrPermission)
- ...
- if errors.Is(err, ErrPermission) ...
+ err := fmt.Errorf("access denied: %w", ErrPermission)
+ ...
+ if errors.Is(err, ErrPermission) ...
** Whether to Wrap
@@ -215,8 +215,8 @@ then a caller cannot look inside to find the `sql.ErrNoRows`. But if
the function instead returns `fmt.Errorf("accessing`DB:`%w",`err)`, then a
caller could reasonably write
- err := pkg.LookupUser(...)
- if errors.Is(err, sql.ErrNoRows) …
+ err := pkg.LookupUser(...)
+ if errors.Is(err, sql.ErrNoRows) …
At that point, the function must always return `sql.ErrNoRows` if you don't want
to break your clients, even if you switch to a different database package. In
@@ -242,23 +242,23 @@ As an example, consider this error inspired by the
which compares an error against a template, considering only fields which are
non-zero in the template:
- type Error struct {
- Path string
- User string
- }
+ type Error struct {
+ Path string
+ User string
+ }
- func (e *Error) Is(target error) bool {
- t, ok := target.(*Error)
- if !ok {
- return false
- }
- return (e.Path == t.Path || t.Path == "") &&
- (e.User == t.User || t.User == "")
- }
+ func (e *Error) Is(target error) bool {
+ t, ok := target.(*Error)
+ if !ok {
+ return false
+ }
+ return (e.Path == t.Path || t.Path == "") &&
+ (e.User == t.User || t.User == "")
+ }
- if errors.Is(err, &Error{User: "someuser"}) {
- // err's User field is "someuser".
- }
+ if errors.Is(err, &Error{User: "someuser"}) {
+ // err's User field is "someuser".
+ }
The `errors.As` function similarly consults an `As` method when present.
@@ -275,18 +275,18 @@ information is needed.
If we wish a function to return an identifiable error condition, such as "item
not found," we might return an error wrapping a sentinel.
- var ErrNotFound = errors.New("not found")
+ var ErrNotFound = errors.New("not found")
- // FetchItem returns the named item.
- //
- // If no item with the name exists, FetchItem returns an error
- // wrapping ErrNotFound.
- func FetchItem(name string) (*Item, error) {
- if itemNotFound(name) {
- return nil, fmt.Errorf("%q: %w", name, ErrNotFound)
- }
- // ...
- }
+ // FetchItem returns the named item.
+ //
+ // If no item with the name exists, FetchItem returns an error
+ // wrapping ErrNotFound.
+ func FetchItem(name string) (*Item, error) {
+ if itemNotFound(name) {
+ return nil, fmt.Errorf("%q: %w", name, ErrNotFound)
+ }
+ // ...
+ }
There are other existing patterns for providing errors which can be semantically
examined by the caller, such as directly returning a sentinel value, a specific
@@ -298,39 +298,39 @@ an error from another package you should convert the error to a form that does
not expose the underlying error, unless you are willing to commit to returning
that specific error in the future.
- f, err := os.Open(filename)
- if err != nil {
- // The *os.PathError returned by os.Open is an internal detail.
- // To avoid exposing it to the caller, repackage it as a new
- // error with the same text. We use the %v formatting verb, since
- // %w would permit the caller to unwrap the original *os.PathError.
- return fmt.Errorf("%v", err)
- }
+ f, err := os.Open(filename)
+ if err != nil {
+ // The *os.PathError returned by os.Open is an internal detail.
+ // To avoid exposing it to the caller, repackage it as a new
+ // error with the same text. We use the %v formatting verb, since
+ // %w would permit the caller to unwrap the original *os.PathError.
+ return fmt.Errorf("%v", err)
+ }
If a function is defined as returning an error wrapping some sentinel or type,
do not return the underlying error directly.
- var ErrPermission = errors.New("permission denied")
-
- // DoSomething returns an error wrapping ErrPermission if the user
- // does not have permission to do something.
- func DoSomething() error {
- if !userHasPermission() {
- // If we return ErrPermission directly, callers might come
- // to depend on the exact error value, writing code like this:
- //
- // if err := pkg.DoSomething(); err == pkg.ErrPermission { … }
- //
- // This will cause problems if we want to add additional
- // context to the error in the future. To avoid this, we
- // return an error wrapping the sentinel so that users must
- // always unwrap it:
- //
- // if err := pkg.DoSomething(); errors.Is(err, pkg.ErrPermission) { ... }
- return fmt.Errorf("%w", ErrPermission)
- }
- // ...
- }
+ var ErrPermission = errors.New("permission denied")
+
+ // DoSomething returns an error wrapping ErrPermission if the user
+ // does not have permission to do something.
+ func DoSomething() error {
+ if !userHasPermission() {
+ // If we return ErrPermission directly, callers might come
+ // to depend on the exact error value, writing code like this:
+ //
+ // if err := pkg.DoSomething(); err == pkg.ErrPermission { … }
+ //
+ // This will cause problems if we want to add additional
+ // context to the error in the future. To avoid this, we
+ // return an error wrapping the sentinel so that users must
+ // always unwrap it:
+ //
+ // if err := pkg.DoSomething(); errors.Is(err, pkg.ErrPermission) { ... }
+ return fmt.Errorf("%w", ErrPermission)
+ }
+ // ...
+ }
* Conclusion