Using the error Interface Effectively

Tutorial 2 of 5

Introduction

The goal of this tutorial is to provide an in-depth understanding of the 'error' interface in Go. This powerful tool allows us to handle errors in a effective and efficient manner. By the end of this tutorial, you will learn:

  • What the 'error' interface is and how it works.
  • How to handle errors in Go using the 'error' interface.
  • Best practices in using the 'error' interface.

Prerequisites: Basic knowledge of Go language is required to fully understand the concepts and examples in this tutorial.

Step-by-Step Guide

In Go, the 'error' interface is a built-in type used to represent an error condition, with the nil value representing no error. It is defined as follows:

type error interface {
    Error() string
}

An error is nothing more than a value that represents failure. If a function or method can fail, it will return an error as its last return value. If it executes successfully, the error will be nil.

Here's a simple example:

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

Best practices when using the 'error' interface:

  • Always check for errors and handle them. Ignoring errors can lead to unexpected results.
  • Use descriptive error messages to make it easier to understand what went wrong.
  • If a function can return an error, always check for this error before using the returned non-error values.

Code Examples

Example 1:

package main

import (
    "errors"
    "fmt"
)

// A function that can return an error
func divide(x, y int) (int, error) {
    if y == 0 {
        return 0, errors.New("cannot divide by zero")
    }
    return x / y, nil
}

func main() {
    result, err := divide(10, 2)

    // Always check the error before using the result
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Println(result)
    }
}

Expected output: 5

Example 2:

package main

import (
    "fmt"
)

// Custom error type
type DivisionError struct {
    dividend, divisor int
}

func (d *DivisionError) Error() string {
    return fmt.Sprintf("cannot divide %d by %d", d.dividend, d.divisor)
}

// Function that returns a custom error
func divide(x, y int) (int, error) {
    if y == 0 {
        return 0, &DivisionError{x, y}
    }
    return x / y, nil
}

func main() {
    _, err := divide(10, 0)

    if err != nil {
        fmt.Println(err)
    }
}

Expected output: cannot divide 10 by 0

Summary

In this tutorial, you learned about the 'error' interface in Go and how to use it effectively. You learned how to return and handle errors in your programs, and how to create custom error types. The next steps would be to further practice error handling in Go and learn more advanced topics like error wrapping.

Practice Exercises

  1. Write a function that takes a filename as argument and returns its content as a string. If the file does not exist, it should return a custom error.

  2. Write a function that accepts a string and tries to convert it to an integer. If the string cannot be converted, it should return an error.

Solutions

  1. Here's one way to do the first exercise:
package main

import (
    "io/ioutil"
    "fmt"
)

type FileError struct {
    filename string
}

func (f *FileError) Error() string {
    return fmt.Sprintf("file %s does not exist", f.filename)
}

func readFile(filename string) (string, error) {
    content, err := ioutil.ReadFile(filename)
    if err != nil {
        return "", &FileError{filename}
    }
    return string(content), nil
}

func main() {
    _, err := readFile("nonexistent.txt")
    if err != nil {
        fmt.Println(err)
    }
}
  1. Here's one way to do the second exercise:
package main

import (
    "strconv"
    "fmt"
)

type ConversionError struct {
    original string
}

func (c *ConversionError) Error() string {
    return fmt.Sprintf("%s cannot be converted to integer", c.original)
}

func convertToInt(s string) (int, error) {
    i, err := strconv.Atoi(s)
    if err != nil {
        return 0, &ConversionError{s}
    }
    return i, nil
}

func main() {
    _, err := convertToInt("abc")
    if err != nil {
        fmt.Println(err)
    }
}