In Go, there are several kinds of errors such as syntax error, compilation errors, etc. In order to make your code reliable and robust, you need to know how to handle different types of errors and how to show more explanatory message to the end users.

Types of Errors

Syntax errors are the mistakes a developer makes in writing the code. It is basically not following the grammar of a programming language. These kinds of errors stop the code from compiling and will need to be fixed before running the program. There are logical errors in which case the logic of the program is not right. These are situations like assigning the wrong value to a variable or checking for wrong state or missing a check of nil, etc. The third set of errors which is something we know that may occur in certain situations will need particular handling. These kinds of errors include dividing a number by zero, reading a file that doesn’t exist.

Most of the times in Go if a function throws an error, it will be part of second return type. For example in strconv package, the functions which can raise an error have return type with second value as error.

1func ParseFloat(s string, bitSize int) (float64, error)
2func ParseInt(s string, base int, bitSize int) (i int64, err error)
3func QuotedPrefix(s string) (string, error)

Example:

 1package main
 2
 3import (
 4	"fmt"
 5	"strconv"
 6)
 7
 8func main() {
 9	var str string = "10"
10	value, error := strconv.Atoi(str)
11	fmt.Println(value, error)
12}

Output:

go run main.go
10 <nil>

Providing Custom Error Messages

We can check if a function has thrown an error by checking second returned value. If the second returned value is nil, that means there was no error else we can provide custom error message using Println statement. If we want to check the exact error type, we can use reflect package as we have seen before.

Example:

 1package main
 2
 3import (
 4	"fmt"
 5	"strconv"
 6	"reflect"
 7)
 8
 9func main() {
10	var str string = "10"
11	value, error := strconv.Atoi(str)
12	fmt.Println(value, error)
13
14	str = "10.5"
15	value, error = strconv.Atoi(str)
16	if error != nil {
17        fmt.Println("Cannot convert", str, "to a number.")
18		fmt.Println("Error:", error)
19		fmt.Println("Error type:", reflect.TypeOf(error))
20	} else {
21		fmt.Println(value)
22	}
23}

This code builds on previous code and checks if there is an error. If an error occurs, we print the error and to check the error type we can use reflect.TypeOf function.

Output:

10 <nil>
Cannot convert 10.5 to a number.
Error: strconv.Atoi: parsing "10.5": invalid syntax
Error type: *strconv.NumError

Error Interface

In Go, the error type is an interface with only one method.

1type error interface {
2    Error() string
3}

Any type that implements this interface is called an error. In your application, you can define custom error types by simply implementing the error interface.

Example:

 1package main
 2
 3import (
 4	"fmt"
 5	// "errors"
 6)
 7
 8type Voter struct {
 9	name string
10	age int
11}
12
13type InvalidAgeError struct {
14	age int
15}
16
17func (e *InvalidAgeError) Error() string {
18	return fmt.Sprintf("Invalid age: %d", e.age)
19}
20
21func(v *Voter) CanVote() (bool, error) {
22	if (v.age < 0 || v.age > 120) {
23		return false, &InvalidAgeError{age: v.age}
24	}
25	return v.age >= 18, nil
26}
27
28func main() {
29	voter := Voter{name: "John", age: 20}
30	canVote, err := voter.CanVote()
31	if err != nil {
32		fmt.Println(err)
33	}
34	fmt.Println("can vote:", canVote)
35
36	voter.age = 121
37	canVote, err = voter.CanVote()
38	if err != nil {
39		fmt.Println(err)
40	}
41	fmt.Println("can vote:",canVote)
42}

Output:

can vote: true
Invalid age: 121
can vote: false

If we want to create custom error messages, we can easily create one using errors.New method. This method takes a string which is the error message. Below example shows its usage.

Example:

 1package main
 2
 3import (
 4	"fmt"
 5	"errors"
 6	"strconv"
 7)
 8
 9type Voter struct {
10	name string
11	age int
12}
13
14func(v *Voter) CanVote() (bool, error) {
15	if (v.age < 0 || v.age > 120) {
16		return false, errors.New("Invalid age: " + strconv.Itoa(v.age))
17	}
18	return v.age >= 18, nil
19}
20
21func main() {
22	voter := Voter{name: "John", age: 20}
23	canVote, err := voter.CanVote()
24	if err != nil {
25		fmt.Println(err)
26	}
27	fmt.Println("can vote:", canVote)
28
29	voter.age = 121
30	canVote, err = voter.CanVote()
31	if err != nil {
32		fmt.Println(err)
33	}
34	fmt.Println("can vote:",canVote)
35}

Output:

an vote: true
Invalid age: 121
can vote: false

Errorf function

In Go, fmt package has a function Errorf which can be helpful when using data from the method to create custom errors. Although we could use errors.New, but it’s somewhat limiting in using data from the method. It looks unclear and we have to import additional packages to convert other number types into string data type. With Errorf we can use Printf style format specifiers. The signature for this function looks like this.

1func Errorf(format string, a ...any) error

This function simply formats using the format specifier and returns a string that satisfies error interface.

 1package main
 2
 3import (
 4	"fmt"
 5)
 6
 7type Voter struct {
 8	name string
 9	age int
10}
11
12func(v *Voter) CanVote() (bool, error) {
13	if (v.age < 0 || v.age > 120) {
14		return false, fmt.Errorf("Invalid age: %d", v.age)
15	}
16	return v.age >= 18, nil
17}
18
19func main() {
20	voter := Voter{name: "John", age: 20}
21	canVote, err := voter.CanVote()
22	if err != nil {
23		fmt.Println(err)
24	}
25	fmt.Println("can vote:", canVote)
26
27	voter.age = 121
28	canVote, err = voter.CanVote()
29	if err != nil {
30		fmt.Println(err)
31	}
32	fmt.Println("can vote:",canVote)
33}

Above code is exactly the same except that it uses fmt.Errorf to return an error. The output is also same as previous output.