Learn Go: Handling Errors in Golang #34

Go does not provide conventional try/catch method to handle the errors, In this article, we are going to explore error handling in Go

Gopher

Lets go!!

Published · Feb 19 2020


Errors indicate an abnormal condition in the program. Let's say we are trying to open a file and the file does not exist in the file system. This is an abnormal condition and it's represented as an error.

Unlike other languages that require developers to handle errors with specialized syntax, errors in Go are values with the type error returned from functions like any other value.

To handle errors in Go, we must examine these errors that functions could return, decide if an error has occurred, and take proper action to protect data and tell users or operators that the error occurred.

. . .

Creating Errors

Before we can handle errors, we need to create some first. The standard library provides two built-in functions to create errors: errors.New and fmt.Errorf. Both of these functions allow you to specify a custom error message that you can later present to your users.

errors.New takes a single argument—an error message as a string that you can customize to alert your users what went wrong.

Try running the following example to see an error created by errors.New printed to standard output:
package main import ( "errors" "fmt" ) func main() { err := errors.New("barnacles") fmt.Println("Sammy says:", err) }


We used the errors.New function from the standard library to create a new error message with the string "barnacles" as the error message.

We’ve followed convention here by using lowercase for the error message as the Go Programming Language Style Guide suggests. Finally, we used the fmt.Println function to combine our error message with "Sammy says:".

The fmt.Errorf function allows you to dynamically build an error message. Its first argument is a string containing your error message with placeholder values such as %s for a string and %d for an integer.

fmt.Errorf interpolates the arguments that follow this formatting string into those placeholders in order:

package main import ( "fmt" "time" ) func main() { err := fmt.Errorf("error occurred at: %v", time.Now()) fmt.Println("An error happened:", err) }
Output An error happened: Error occurred at: 2019-07-11 16:52:42.532621 -0400 EDT m=+0.000137103

We used the fmt.Errorf function to build an error message that would include the current time. The formatting string we provided to fmt.Errorf contains the %v formatting directive that tells fmt.Errorf to use the default formatting for the first argument provided after the formatting string.

That argument will be the current time, provided by the time.Now function from the standard library. Similarly to the earlier example, we combine our error message with a short prefix and print the result to standard output using the fmt.Println function.
. . .

Handling Errors

Typically you wouldn’t see an error created like this to be used immediately for no other purpose, as in the previous example. In practice, it’s far more common to create an error and return it from a function when something goes wrong.

Callers of that function will then use an if statement to see if the error was present or nil—an uninitialized value.

This next example includes a function that always returns an error. Notice when you run the program that it produces the same output as the previous example even though a function is returning the error this time. Declaring an error in a different location does not change the error’s message.
package main import ( "errors" "fmt" ) func boom() error { return errors.New("barnacles") } func main() { err := boom() if err != nil { fmt.Println("An error occurred:", err) return } fmt.Println("Anchors away!") }
Output An error occurred: barnacles

Here we define a function called boom() that returns a single error that we construct using errors.New. We then call this function and capture the error with the line err := boom().
Once we assign this error, we check to see if it was present with the if err != nil conditional. Here the conditional will always evaluate to true, since we are always returning an error from boom().

This won’t always be the case, so it’s good practice to have logic handling cases where an error is not present (nil) and cases where the error is present.

When the error is present, we use fmt.Println to print our error along with a prefix as we have done in earlier examples. Finally, we use a return statement to skip the execution of fmt.Println("Anchors away!"), since that should only execute when no error occurred.
. . .

Returning Errors Alongside Values

Functions that return a single error value are often those that effect some stateful change, like inserting rows to a database. It’s also common to write functions that return a value if they completed successfully along with a potential error if that function failed.

Go permits functions to return more than one result, which can be used to simultaneously return a value and an error type.

To create a function that returns more than one value, we list the types of each returned value inside parentheses in the signature for the function.

For example, a capitalize function that returns a string and an error would be declared using func capitalize(name string) (string, error) {}. The (string, error) part tells the Go compiler that this function will return a string and an error, in that order.

Run the following program to see the output from a function that returns both a string and an error:
package main import ( "errors" "fmt" "strings" ) func capitalize(name string) (string, error) { if name == "" { return "", errors.New("no name provided") } return strings.ToTitle(name), nil } func main() { name, err := capitalize("sammy") if err != nil { fmt.Println("Could not capitalize:", err) return } fmt.Println("Capitalized name:", name) }
Output Capitalized name: SAMMY

We define capitalize() as a function that takes a string (the name to be capitalized) and returns a string and an error value. In main(), we call capitalize() and assign the two values returned from the function to the name and err variables by separating them with commas on the left-hand side of the := operator.

After this, we perform our if err != nil check as in earlier examples, printing the error to standard output using fmt.Println if the error was present. If no error was present, we print Capitalized name: SAMMY.

Try changing the string "sammy" in name, err := capitalize("sammy") to the empty string ("") and you’ll receive the error Could not capitalize: no name provided instead.

The capitalize function will return an error when callers of the function provide an empty string for the name parameter. When the name parameter is not the empty string, capitalize() uses strings.ToTitle to capitalize the name parameter and returns nil for the error value.
. . .

Do not ignore errors

Never ever ignore an error. Ignoring errors is inviting for trouble. Let me rewrite the example which lists the name of all files that match a pattern ignoring the error handling code.
package main

import (
"errors"
"fmt"
"strings"
)

func capitalize(name string) (string, error) {
if name == "" {
return "", errors.New("no name provided")
}
return strings.ToTitle(name), nil
}

func main() {
name, _ := capitalize("")

fmt.Println("Capitalized name:", name)
}


I have ignored the error returned by the capitalize function by using the _ blank identifier in line no. 17. This program will print,
Capitalized name:

Since we ignored the error, the output seems as if there is no name but actually the input itself is malformed. So never ignore errors.
. . .

Conclusion

We’ve seen many ways to create errors using the standard library and how to build functions that return errors in an idiomatic way.

In this tutorial, we’ve managed to successfully create various errors using the standard library errors.New and fmt.Errorf functions. In future tutorials, we’ll look at how to create our own custom error types to convey richer information to users.
. . .

. . .
References:
Snehal Kumar

Jul 28 2019

Write your response...

On a mission to build Next-Gen Community Platform for Developers