Go Error Handling Tutorial

In this Go tutorial we learn about the Go defer-panic-recover error handling system.

We learn how to return a custom error from a function. We also learn about panic, which is Go's version of an exception. Then we learn how to defer execution of a statement and how to recover execution.

The Go error handling system

Instead of the typical try/catch mechanisms in programming languages, Go provides us with a defer-panic-recover system.

Before we can use the error handling system, we have to import the ‘errors’ package.

Example:
package main

import "errors"

func main() {}

We can handle custom errors with the built-in error interface type.

Example:
type error interface {

    Error() string
}

We can use errors.New() to construct an error message.

Syntax:
 variable_name := errors.New("Error message")

We can then use this error message when something goes wrong in our code, for example, the wrong argument type in a function.

Return an error from a function

A function can return an error alongside any other return values it has.

To do so, we specify the return type as error, and in the logic set up a condition where the error would be returned.

Example:
package main

import "errors"

func main() {}

// return an int and an error
func add(num1 int, num2 int) (int, error) {

    err := errors.New("Number cannot be negative")

    if(num1 < 0 || num2 < 0) {
        return 0, err
    }

    return num1 + num2, nil
}

In the example above, we return both an int value and an error. If either of the numbers in the argument list is less than 0, we would return a normal result of 0 and the error message we set up.

Otherwise, the function would return the calculated value and nil for the error.

When we call the function, we can do a simple !nil check to see if an error has been raised.

Example:
package main

import "errors"
import "fmt"

func main() {

    result, err := add(5, -3)

    if(err != nil) {

        fmt.Println(err)
    } else {

        fmt.Println(result)
    }
}

func add(num1 int, num2 int) (int, error) {

    err := errors.New("Number cannot be negative")

    if(num1 < 0 || num2 < 0) {
        return 0, err
    }

    return num1 + num2, nil
}

In the example above, we test if the second return value (the error) is not nil. If it isn’t, it means there is an error so we can print the error message we defined in the function.

If it is nil, the calculation succeeded and we can print the result of the calculation.

Panic

In Go, panic is similar to an exception. Panic means that something unexpected happened and execution of the app is stopped.

Panic can be manually thrown by the developer, or can happen because of an error during runtime, like an out of range array.

Example:
package main

import "fmt"

func main() {

    var groceryList = [] string {

        "Bread",
        "Milk",
        "Cheese",
    }

    fmt.Println( groceryList[5] )
}

In the example above, we try to access array element 5, but the array only has 3 elements so the program panics and stops.

Output:
 panic: runtime error: index out of range [5] with length 3

As mentioned earlier, we can manually throw a panic. Let’s modify our earlier example to use panic instead of error.

Example:
package main

import "fmt"

func main() {

    result := add(5, -3)

    fmt.Println(result)
}

func add(num1 int, num2 int) int {

    if(num1 < 0 || num2 < 0) {
        panic("Number cannot be negative")
    }

    return num1 + num2
}

This time, in the function body we throw a panic immediately when the number is negative instead of returning an error message.

Defer

The defer keyword delays the execution of a function or a statement until the end of the function that calls it.

To use defer we simply add the keyword before a function call.

Example:
package main

import "fmt"

func main() {

    defer consoleLog("Hello")
    consoleLog("there")
}

func consoleLog(msg string) {

    fmt.Println(msg)
}

In the example above, we defer the execution of the consoleLog function until after any other consoleLog functions are called.

Output:
there
Hello

Recover

We use recover() to resume normal execution after the program panics.

We use recover from inside a deferred function/lambda. It will retrieve the error value through the panic call, or it will return nil.

Example:
package main

import "fmt"

func main() {

    // divide by 0
    result := div(5, 0)
    fmt.Println(result)

    result = div(5, 2)
    fmt.Println(result)
}

func div(num1 float64, num2 float64) float64 {

    defer func() {

        fmt.Println(recover())
    }()

    return num1 / num2
}

In the example above, our first function call will divide by zero, causing the program to panic. But, the program still executes the second statement because the deferred lambda inside the function recovers from the panic.

We can print recover() because it retrieves the error value from the panic.

Summary: Points to remember

  • Go provides us with a simple defer-panic-recover system to handle errors.
  • We can create a custom error with errors.New().
  • We can return custom errors from a function by specifying error as the return type.
  • Panic will stop execution of the program when manually thrown or when the program encounters a problem at runtime.
  • Defer will defer the execution of a function until all functions with the same name has been executed.
  • Recover will attempt to resume execution after a panic and is called from within a deferred function/lambda.