Introduction
It is idiomatic in Go to use the error interface type as the return type for any error that is going to be returned from a function or method. This interface is used by all the functions and methods in the standard library that return errors. For example, here is the declaration for the Get method from the http package:
Listing 1.1
http://golang.org/pkg/net/http/#Client.Get
func (c *Client) Get(url string) (resp *Response, err error)
Listing 1.1 shows how the second return argument for the
Get method is an interface value of type
error. Handling errors that are returned from functions and methods starts by checking if the returned interface value of type
error is not
nil:
Listing 1.2 resp, err := c.Get("[https://www.ardanlabs.com/blog/index.xml](/broken-link) ")
if err != nil {
log.Println(err)
return
}
In listing 1.2, a call to
Get is performed and the return values are assigned to local variables. Then the value of the
err variable is compared to the value of
nil. If the value is not
nil, then there was an error.
Because an interface is being used to handle error values, a concrete type needs to be declared that implements the interface. The standard library has declared and implemented this concrete type for us in the form of a struct called
errorString. In this post, we will explore the implementation and use of the
error interface and
errorString struct from the standard library.
Error Interface and errorString StructThe declaration of the
error interface is provided to us by the language directly:
Listing 1.3 http://golang.org/pkg/builtin/#error
type error interface {
Error() string
}
In listing 1.3, we can see how the
error interface is declared with a single method called
Error that returns a
string. Therefore, any type that implements the
Error method will implement the interface and can be used as an interface value of type
error. If you are not familiar with how interfaces work in Go, read my post about
Interfaces, Methods and Embedded Types.
The standard library has also declared a struct type called
errorString that can be found in the
errors package:
Listing 1.4 http://golang.org/src/pkg/errors/errors.go
type errorString struct {
s string
}
In listing 1.4, we can see how the declaration of
errorString shows a single field named
s of type
string. Both the type and its single field are unexported, which means we can’t directly access the type or its field. To learn more about unexported identifiers in Go, read my post about
Exported/Unexported Identifiers in Go.
The
errorString struct implements the error interface:
Listing 1.5 http://golang.org/src/pkg/errors/errors.go
func (e *errorString) Error() string {
return e.s
}
The
error interface is implemented with a pointer receiver as seen in listing 1.5. This means only pointers of type
errorString can be used as an interface value of type
error. Also, since the
errorString type and its single field are unexported, we can’t perform a type assertion or conversion of the
error interface value. Our only access to the value of the concrete type is with the
Error method.
The
errorString type is the most common type of error that is returned as an interface value of type
error within the standard library. Now that we know what these types look like, let’s learn how the standard library gives us the ability to create an interface value of type
error using the
errorString struct.
Creating Error ValuesThe standard library provides two ways to create pointers of type
errorString for use as an interface value of type
error. When all you need for your error is a string with no special formatting, the
New function from the
errors package is the way to go:
Listing 1.6 var ErrInvalidParam = errors.New("mypackage: invalid parameter")
Listing 1.6 shows a typical call to the
New function from the
errors package. In this example, an interface variable of type
error is declared and initialized after the call to
New. Let’s look at the declaration and implementation of the
New function:
Listing 1.7 http://golang.org/src/pkg/errors/errors.go
// New returns an error that formats as the given text.
func New(text string) error {
return &errorString{text}
}
In the declaration of the
New function in listing 1.7, we see the function takes a
string as a parameter and returns an interface value of type
error. In the implementation of the function, a pointer of type
errorString is created. Then on the return statement, an interface value of type
error is created by the compiler and bound to the pointer to satisfy the return argument. The
errorString pointer becomes the underlying data value and type for the interface
error value that is returned.
When you have an error message that requires formatting, use the
Errorf function from the
fmt package:
Listing 1.8 var ErrInvalidParam = fmt.Errorf("invalid parameter [%s]", param)
Listing 1.8 shows a typical call to the
Errorf function. If you are familiar with using the other format functions from the
fmt package, then you will notice this works the same. Once again, an interface variable of type
error is declared and initialized after the call to
Errorf.
Let’s look at the declaration and implementation of the
Errorf function:
Listing 1.9 http://golang.org/src/pkg/fmt/print.go
// Errorf formats according to a format specifier and returns the string
// as a value that satisfies error.
func Errorf(format string, a ...interface{}) error {
return errors.New(Sprintf(format, a…))
}
In the declaration of the
Errorf function in listing 1.9, we see the
error interface type is being used once again as the return type. In the implementation of the function, the
New function from the
errors package is used to create an interface value of type
error for the message that is formatted. So whether you use the
errors or
fmt package to create your interface value of type
error, the value underneath is always a pointer of type
errorString.
Now we know the two different ways we can create interface values of type
error using a pointer of type
errorString. Next, let’s learn how packages in the standard library provide support for comparing unique errors that are returned from API calls.
Comparing Error ValuesThe
bufio package, like many other packages in the standard library, uses the
New function from the
errors package to create package level error variables:
Listing 1.10 http://golang.org/src/pkg/bufio/bufio.go
var (
ErrInvalidUnreadByte = errors.New("bufio: invalid use of UnreadByte")
ErrInvalidUnreadRune = errors.New("bufio: invalid use of UnreadRune")
ErrBufferFull = errors.New("bufio: buffer full")
ErrNegativeCount = errors.New("bufio: negative count")
)
Listing 1.10 shows four package level error variables that are declared and initialized in the
bufio package. Notice each error variable starts with the prefix
Err. This is a convention in Go. Since these variables are declared as interface type
error, we can use these variables to identify specific errors that are returned by the different
bufio package API’s:
Listing 1.11 data, err := b.Peek(1)
if err != nil {
switch err {
case bufio.ErrNegativeCount:
// Do something specific.
return
case bufio.ErrBufferFull:
// Do something specific.
return
default:
// Do something generic.
return
}
}
In listing 1.11, the code example calls into the
Peek method from a pointer variable of type
bufio.Reader. The
Peek method has the potential of returning both the
ErrNegativeCount and
ErrBufferFull error variables. Because these variables have been exported by the package, we now have the ability to use the variables to identify which specific error message was returned. These variable become part of the packages API for error handling.
Imagine if the
bufio package did not declare these error variables. Now we would need to compare the actual error messages to determine which error we received:
Listing 1.12 data, err := b.Peek(1)
if err != nil {
switch err.Error() {
case "bufio: negative count":
// Do something specific.
return
case "bufio: buffer full":
// Do something specific.
return
default:
// Do something specific.
return
}
}
There are two problems with the code example in listing 1.12. First, the call to
Error() requires a copy of the error message to be made for the
switch statement. Second, if the package author ever changes these messages, this code breaks.
The
io package is another example of a package that declares interface type
error variables for the errors that can be returned:
Listing 1.13 http://golang.org/src/pkg/io/io.go
var ErrShortWrite = errors.New("short write")
var ErrShortBuffer = errors.New("short buffer")
var EOF = errors.New("EOF")
var ErrUnexpectedEOF = errors.New("unexpected EOF")
var ErrNoProgress = errors.New("multiple Read calls return no data or error")
Listing 1.13 shows six package level error variables that are declared in the
io package. The third variable is the declaration of the
EOF error variable that is returned to indicate when there is no more input available. It is common to compare the error value from functions in this package with the
EOF variable.
Here is the implementation of the
ReadAtLeast function from inside
io package:
Listing 1.14 http://golang.org/src/pkg/io/io.go
func ReadAtLeast(r Reader, buf []byte, min int) (n int, err error) {
if len(buf) < min {
return 0, ErrShortBuffer
}
for n < min && err == nil {
var nn int
nn, err = r.Read(buf[n:])
n += nn
}
if n >= min {
err = nil
} else if n > 0 && err == EOF {
err = ErrUnexpectedEOF
}
return
}
The
ReadAtLeast function in listing 1.14 shows the use of these error variables in action. Notice how the error variables
ErrShortBuffer and
ErrUnexpectedEOF are used as a return value. Also notice how the function compares the
err variable against the
EOF variable, just like we do in our own code.
This pattern of creating error variables for the errors your API’s are going to return is something you should consider implementing yourself. It helps provide an API for errors and keeps error handling performant.
Why Not a Named TypeOne question that comes up is why didn’t the language designers use a named type for
errorString?
Let’s take a look at the implementation using a named type and compare it to using the struct type:
Listing 1.15 http://play.golang.org/p/uZPi4XKMF9
01 package main
02
03 import (
04 "errors"
05 "fmt"
06 )
07
08 // Create a named type for our new error type.
09 type errorString string
10
11 // Implement the error interface.
12 func (e errorString) Error() string {
13 return string(e)
14 }
15
16 // New creates interface values of type error.
17 func New(text string) error {
18 return errorString(text)
19 }
20
21 var ErrNamedType = New("EOF")
22 var ErrStructType = errors.New("EOF")
23
24 func main() {
25 if ErrNamedType == New("EOF") {
26 fmt.Println("Named Type Error")
27 }
28
29 if ErrStructType == errors.New("EOF") {
30 fmt.Println("Struct Type Error")
31 }
32 }
Output:
Named Type Error
Listing 1.15 provides a sample program to show a problem surrounding the use of a named type for
errorString. The program on line 09 declares a named typed called
errorString of type
string. Then on line 12, the
error interface is implemented for the named type. To simulate the
New function from the
errors package, a function called
New is implemented on line 17.
Then on lines 21 and 22, two error variables are declared and initialized. The
ErrNamedType variable is initialized using the
New function and the
ErrStructType is initialized using the
errors.New function. Finally in
main(), the variables are compared to new values created by the same functions.
When you run the program, the output is interesting. The
if statement on line 25 is
true and the
if statement on line 29 is
false. By using the named type, we are able to create new interface values of type
error with the same error message and they match. This poses the same problem as in listing 1.12. We could create our own versions of the
error values and use them. If at any time the package author changes the messages, our code will break.
The same problem can occur when
errorString is a struct type. Look at what happens when a value receiver is used for the implementation of the
error interface:
Listing 1.16 http://play.golang.org/p/EMWPT-tWp4
01 package main
02
03 import (
04 "fmt"
05 )
06
07 type errorString struct {
08 s string
09 }
10
11 func (e errorString) Error() string {
12 return e.s
13 }
14
15 func NewError(text string) error {
16 return errorString{text}
17 }
18
19 var ErrType = NewError("EOF")
20
21 func main() {
22 if ErrType == NewError("EOF") {
23 fmt.Println("Error:", ErrType)
24 }
25 }
Output:
Error: EOF
In listing 1.16 we have implemented the
errorString struct type using a value receiver for the implementation of the
error interface. This time we experience the same behavior as we did in listing 1.15 with the named type. When interface type values are compared, the values of the concrete type are compared underneath.
By the standard library using a pointer receiver for the implementation of the
error interface for the
errorString struct, the
errors.New function is forced to return a pointer value. This pointer is what is bound to the interface value and will be unique every time. In these cases, pointer values are being compared and not the actual error messages.
ConclusionIn this post we created a foundation for understanding what the
error interface is and how it is used in conjunction with the
errorString struct. Using the
errors.New and
fmt.Errorf functions for creating interface values of type
error is a very common practice in Go and highly recommended. Usually a simple string based error message with some basic formatting is all we need to handle errors.
We also explored a pattern that is used by the standard library to help us identify the different errors that are returned by API calls. Many packages in the standard library create these exported error variables which usually provide enough granularity to identify one particular error over the other.
There are times when creating your own custom error types make sense. This is something we will explore in part II of this post. For now, use the support provided to us by the standard library for handling errors and follow its example.
Read Part II:
https://www.ardanlabs.com/blog/2014/11/error-handling-in-go-part-ii.html