The error data type
Go provides a special data type for representing error conditions and error messages named error
—in practice, this means that Go treats errors as values. In order to program successfully in Go, you should be aware of the error conditions that might occur with the functions and methods you are using and handle them accordingly.
As you already know from the previous chapter, Go follows the next convention about error
values: if the value of an error
variable is nil
, then there was no error. As an example, let us consider strconv.Atoi()
, which is used for converting a string
value into an int
value (Atoi
stands for ASCII to Int). As specified by its signature, strconv.Atoi()
returns (int, error)
. Having an error
value of nil
means that the conversion was successful and that you can use the int
value if you want. Having an error
value that is not nil
means that the conversion was unsuccessful and that the string
input is not a valid int
value.
If you want to learn more about strconv.Atoi()
, you should execute go doc strconv.Atoi
in your terminal window.
You might wonder what happens if you want to create your own error messages. Is this possible? Should you wish to return a custom error, you can use errors.New()
from the errors
package. This usually happens inside a function other than main()
because main()
does not return anything to any other function. Additionally, a good place to define your custom errors is inside the Go packages you create.
You will most likely work with errors in your programs without needing the functionality of the errors
package. Additionally, you do not need to define custom error messages unless you are creating big applications or packages.
If you want to format your error messages in the way fmt.Printf()
works, you can use the fmt.Errorf()
function, which simplifies the creation of custom error messages—the fmt.Errorf()
function returns an error
value just like errors.New()
.
And now we should talk about something important: you should have a global error handling tactic in each application that should not change. In practice, this means the following:
- All error messages should be handled at the same level, which means that all errors should either be returned to the calling function or be handled at the place they occurred.
- It should be clearly documented how to handle critical errors. This means that there will be situations where a critical error should terminate the program and other times where a critical error might just create a warning message onscreen.
- It is considered a good practice to send all error messages to the log service of your machine because this way the error messages can be examined at a later time. However, this is not always true, so exercise caution when setting this up—for example, cloud native apps do not work that way.
The error
data type is actually defined as an interface—interfaces are covered in Chapter 4, Reflection and Interfaces.
Type the following code in your favorite text editor and save it as error.go
in the directory where you put the code for this chapter. Using ch02
as the directory name is a good idea.
package main
import (
"errors"
"fmt"
"os"
"strconv"
)
The first part is the preamble of the program—error.go
uses the fmt
, os
, strconv
, and errors
packages.
// Custom error message with errors.New()
func check(a, b int) error {
if a == 0 && b == 0 {
return errors.New("this is a custom error message")
}
return nil
}
The preceding code implements a function named check()
that returns an error
value. If both input parameters of check()
are equal to 0
, the function returns a custom error message using errors.New()
—otherwise it returns nil
, which means that everything is OK.
// Custom error message with fmt.Errorf()
func formattedError(a, b int) error {
if a == 0 && b == 0 {
return fmt.Errorf("a %d and b %d. UserID: %d", a, b, os.Getuid())
}
return nil
}
The previous code implements formattedError()
, which is a function that returns a formatted error message using fmt.Errorf()
. Among other things, the error message prints the user ID of the user that executed the program with a call to os.Getuid()
. When you want to create a custom error message, using fmt.Errorf()
gives you more control over the output.
func main() {
err := check(0, 10)
if err == nil {
fmt.Println("check() ended normally!")
} else {
fmt.Println(err)
}
err = check(0, 0)
if err.Error() == "this is a custom error message" {
fmt.Println("Custom error detected!")
}
err = formattedError(0, 0)
if err != nil {
fmt.Println(err)
}
i, err := strconv.Atoi("-123")
if err == nil {
fmt.Println("Int value is", i)
}
i, err = strconv.Atoi("Y123")
if err != nil {
fmt.Println(err)
}
}
The previous code is the implementation of the main()
function where you can see the use of the if err != nil
statement multiple times as well as the use of if err == nil
, which is used to make sure that everything was OK before executing the desired code.
Running error.go
produces the next output:
$ go run error.go
check() ended normally!
Custom error detected!
a 0 and b 0. UserID: 501
Int value is -123
strconv.Atoi: parsing "Y123": invalid syntax
Now that you know about the error
data type, how to create custom errors, and how to use error
values, we'll continue with the basic data types of Go that can be logically divided into two main categories: numeric data types and non-numeric data types. Go also supports the bool
data type, which can have a value of true
or false
only.