Evendyne
Published in

Evendyne

Getting started with Go: Error Handling

Error handling is an essential aspect of software development, and Go is no exception. In this article of the series, I am going to show you an overview on Go’s fairly unique approach to handling errors.

In Go, errors are represented by the error type, which is a built-in interface with a single method: Error() string. This method returns a string describing the error. It is important to note, that you to use the nil value to represent the absence of an error.
Now let’s see an example of how you can create an error:

func div(x, y int) (int, error) {
if y == 0 {
return 0, fmt.Errorf("cannot divide by zero")
}
return x / y, nil
}

Here the div function takes two integers and returns their quotient. If the second argument is zero, the function returns an error with the message "cannot divide by zero". Otherwise, it returns the quotient and a nil error, which indicates that no error occurred.

To create custom errors, you can use the errors.New function on the top of the package, so the error is not created each time the function is called:

import (
"errors"
)

errDivideByZero := errors.New("cannot divide by zero")

func div(x, y int) (int, error) {
if y == 0 {
return 0, errDivideByZero
}
return x / y, nil
}

To check for errors in Go, you can use the if statement with the err variable. Look at an example of how you can use the div function:

result, err := div(10, 2)
if err != nil {
fmt.Println(err)
return
}

fmt.Println(result)

It’s also common to see error handling in Go using the defer keyword, which allows you to defer the execution of a function until the surrounding function returns:

f, err := os.Open("file.txt")
if err != nil {
fmt.Println(err)
return
}
defer f.Close()

In the example above, the Open function is called to open the file "file.txt". If there's an error, it is printed to the screen and the function returns. Otherwise, the file is opened and the Close function is deferred until the surrounding function returns. This ensures that the file is always closed, even if there's an error.

There are also several other ways to handle errors in Go, such as using the panic and recover functions to handle runtime errors. As in most cases, panic should only be used in main.go, handling errors gracefully is the recommended technique.

Sometimes, you may want to wrap an error with additional context before returning it. You can do this using the fmt.Errorf function, which allows you to create a new error with a formatted string. For example:

_, err := os.Stat("file.txt")
if os.IsNotExist(err) {
return fmt.Errorf("file not found: %w", err)
}

In this short snippet, the Stat function is used to check if the file "file.txt" exists. If the file doesn't exist, the IsNotExist function returns true and a new error is returned with the message "file not found" and the original error as the cause.

In some cases, you may want to return an error from a function if it is unable to complete its task due to an error that it encountered while calling another method. You can do this using the errors.Wrap function, which allows you to wrap an error with additional context:

_, err := os.Open("file.txt")
if err != nil {
return errors.Wrap(err, "failed to open file")
}

The example above can be useful for providing more context when debugging an error.

You can make your own custom error types with additional methods or fields, by creating a new type that implements the error interface. For example:

type DivisionByZeroError struct {
message string
}

func (e *DivisionByZeroError) Error() string {
return e.message
}

func div(x, y int) (int, error) {
if y == 0 {
return 0, &DivisionByZeroError{"division by zero"}
}
return x / y, nil
}

Here the DivisionByZeroError type is defined with a field message and a method Error that returns the error message. The div function returns a DivisionByZeroError if the second argument is zero, otherwise it returns the quotient and a nil error.

Type switches can be useful for handling errors that have additional information or custom behavior beyond the basic error interface. They allow you to handle different types of errors differently and take appropriate action based on the specific error type:

func handleErrors(err error) {
switch v := err.(type) {
case MyError:
fmt.Printf("MyError: %v\n", v)
case *MyError:
fmt.Printf("*MyError: %v\n", v)
default:
fmt.Printf("Other error: %v\n", v)
}
}

Here the handleError() function takes an error value as an argument and uses a type switch to handle different types of errors. The MyError type is a custom error type that implements the error interface. The type switch handles both the concrete MyError type and the pointer *MyError type. If the error value is not of either of these types, it is handled by the default case.

Pros vs Cons

In Go, the use of the error type as a first-class value allows for more flexible error handling than languages that rely on exceptions: the error handling model takes the “plan for failure, not success” approach. Additionally, the lack of exceptions can make it easier to reason about the behavior of a Go app, as it is clear which errors are expected and how they are being handled.
However, there is another side of the coin when it comes to how error handling is implemented in Go. As mentioned above, Go does not have exceptions, so errors must be explicitly checked and handled. This can lead to repetitive error-checking, which can make the code less maintainable. What is more, the error type is an interface, so it requires the use of type assertions or type switches to access the underlying error value. This can be quite verbose.

That’s A Wrap

Go’s unique error handling model is highly debated in the engineering community, but in my oppinion, the explicit error handling nature and traiting errors first is a reliable way for more robust applications.

In the next article of the Getting started with Go series, we’ll explore the concurrency topic.

You can find more about Evendyne here.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store