GuidesHow to Handle Errors in Go

How to Handle Errors in Go

Error handling is a mechanism added to many modern programming languages which typically defines how the code is going to react under unexpected circumstances. Some syntactic and semantic errors are directly dealt with by the compiler, who reports them as they are detected by the static checker. But there are some that need special handling. Many languages use specialized syntax to deal with errors that are a bit complex in their syntactical structure. Go is more simplistic and uses values to denote the type of error returned from functions like any other value. The article introduces the error handling mechanism of Go with some examples.

If you are unfamiliar with working with functions, you may want to read our article on the subject: Understanding Functions in Go.

Go Error Handling using the errors.New Functions

There is a package called errors in the Go standard library which implements functions to manipulate errors. The errors.New is a built-in function to create an error and specify a custom error message that can be shown to the user if such an error occurs. This function takes a single argument string which typically is the message we want to display and alarm the user regarding the error.

Let’s write a simple program that checks the validity of an email address and presents a built-in error message due to an invalid email address using Go:

package main

import (
	"fmt"
	"net/mail"
)

func isMailAddressValid(email string) (string, bool) {
	addr, err := mail.ParseAddress(email)
	if err != nil {
		return err.Error(), false		
	}
	return addr.Address, true
}

func main() {
	em, ok := isMailAddressValid("mymail**gmail.com")
	if ok {
		fmt.Println(em)
	} else {
		fmt.Println(em)
	}
}

If you run this code in your integrated development environment (IDE) or code editor, you get the following output:

mail: missing '@' or angle-addr

The above example simply displays the error message returned by mail.ParseAddress() function. This function takes a string value as an argument and parses according to the RFC 5322 address rules. It returns a struct type Address object and an error interface object replenished with a string message. The ParseAddress is a function found in the mail package.

We can create our own error object with a custom error message using the errors.New function. Let’s modify the above program to display our own custom error message:

package main

import (
	"errors"
	"fmt"
	"net/mail"
)

func isMailAddressValid(email string) (string, bool) {
	addr, err := mail.ParseAddress(email)
	customErrMsg := errors.New("please provide a valid email address")
	if err != nil {		
		return customErrMsg.Error(), false
	}
	return addr.Address, true
}

func main() {
	em, ok := isMailAddressValid("mymail**gmail.com")
	if ok {
		fmt.Println(em)
	} else {
		fmt.Println(em)
	}
}

Once more, running this code in your code editor will result in the following output:

please provide a valid email address

Error Handling in Go with the fmt.Errorf function

There is another function in the fmt package called fmt.Errorf. This function enables us to create error messages dynamically and formats the error message according to the format specifier, returning a string value. The function takes a string argument with a placeholder much like the function fmt.printf.

According to the Go documentation: “if the format specifier includes a %w verb with an error operand, the returned error will implement an Unwrap method returning the operand. It is invalid to include more than one %w verb or to supply it with an operand that does not implement the error interface. The %w verb is otherwise a synonym for %v.”

Here is a quick example with a slight modification of the above code:

package main

import (
	"fmt"
	"net/mail"
)

func isMailAddressValid(email string) (string, bool) {
	addr, e := mail.ParseAddress(email)

	if e != nil {
		err := fmt.Errorf("you provided %s, valid format is [email protected]", email)
		return err.Error(), false
	}
	return addr.Address, true
}

func main() {
	em, ok := isMailAddressValid("mymail**gmail.com")
	if ok {
		fmt.Println(em)
	} else {
		fmt.Println(em)
	}
}

Here is the output of the above code:

you provided mymail**gmail.com, valid format is [email protected]

Go Function Returning Error Values

In practice, an error is returned from a function when something goes wrong. The callers of the function typically use an if statement to see the occurrence of an error or nil value, which essentially means no error has occurred. A function that returns a single error value only to denote some stateful changes – such as if the socket connection is successful or database row changes have occurred and so forth. A Function commonly returns an error value to indicate its success or to indicate an error which simply means the function has failed. Go programming allows a function to return more than one value. For example, the following intDiv function returns the result of an integer division of two values – a numerator and a denominator. An error is returned along with an invalid value (for example, -999) if the denominator value is 0 (because dividing by zero is not allowed). If everything is fine then a nil is returned as an error value.

package main

import (
	"errors"
	"fmt"
)

func intDiv(num, denom int) (int, error) {
	if denom == 0 {
		return -999, errors.New("divide by zero is no allowed")
	}
	return num / denom, nil
}

func main() {
	n := 20
	d := 0

	result, err := intDiv(n, d)
	if err == nil {
		fmt.Println("Result : ", result)
	} else {
		fmt.Println(err.Error())
	}
}

Here is the result of running the above Go code sample:

divide by zero is no allowed

It is but a convention that the error returned by the function is the last item in the event that a function returns multiple values.

Using Anonymous Functions for Error Handling in Go

We can use anonymous functions to reduce boilerplate codes in error handling. The Following is a slight modification of the above code for handling errors with anonymous functions in Go:

package main

import (
	"errors"
	"fmt"
)

func intDiv(num, denom int) (int, error) {

	he := func(err error) (int, error) {
		return -999, err
	}
	if denom == 0 {
		return he(errors.New("divide by zero is no allowed"))
	}
	return num / denom, nil
}

func main() {

	n := 20
	d := 3

	result, err := intDiv(n, d)
	if err == nil {
		fmt.Println("Result : ", result)
	} else {
		fmt.Println(err.Error())
	}

}

Go Error Handling Using Blank Identifier

Go enables developers to completely ignore an error as it is received from a function. It seems not a very good idea yet sometimes it may be necessary. For example, we may receive the result from the integer division in the above code but ignore receiving the error. Here is an example of using a blank identifier in Go:

//...

func main() {
	n := 20
	d := 3
	result, _ := intDiv(n, d)	
	fmt.Println("Result : ", result)
}

The underscore (_) means a blank identifier, which can be assigned or declared with any value of any type, with the value discarded harmlessly. In the above code, we require assignment of multiple values on the left side. Since we want to ignore the error value we can use the underscore(_). It helps to avoid creating dummy variables and makes it clear the fact that the value is to be discarded. So, syntactically, the following is also correct:

//...
	_, err := intDiv(n, d)	
//...

However, the following is incorrect:

//...
	_, _ := intDiv(n, d)	// not allowed
//...

We may, however, write it as follows, which means completely discarding anything returned by the function:

//...
	intDiv(n, d)	// OK
//...

Go Error Handling Techniques

As we can see, the Go programming language provides many ways to create errors through the standard library and many ways to build functions that can take care of the error and return them to the caller. It is also possible to create and handle more complex customized errors according to the need of the developer. This flexibility is inherent in Golang and we’ll go into those details in a later writeup. Here, we glimpsed quickly the error handling mechanisms implemented in Go with some relevant examples.

In the meantime, feel free to read more tutorials on Golang programming:

Latest Posts

Related Stories