• Part 31: Custom Errors
  • By Naveen R
  • Please note the source.

In the last tutorial, we learned how errors are represented in the Go language and how to handle them using the standard library. We also learned how to extract more error information from the standard library.

This tutorial shows you how to use our own custom errors in our functions and packages, and we will use the same techniques used by the standard library to provide more information about our custom errors.

useNewFunction create customerror

The easiest way to create a custom error is to use the New function of the Errors package.

Before we use the New function to create a custom error, let’s look at how it is implemented. An implementation of the New function in the Errors package is provided below.

// Package errors implements functions to manipulate errors.
  package errors

  // New returns an error that formats as the given text.
  func New(text string) error {
      return &errorString{text}
  }

  // errorString is a trivial implementation of error.
  type errorString struct {
      s string
  }

  func (e *errorString) Error(a) string {
      return e.s
  }
Copy the code

The implementation is very simple. ErrorString is a structure type with a single string S. The error method of the error interface is implemented in line 14 with the errorString pointer sink.

The New function on line 5 takes a string argument that creates a value of type errorString and returns its address, completing a New error.

Now that we know how the New function works, let’s use it in our own program to create a custom error.

We will create a simple program that calculates the area of a circle, returning error if the radius is negative.

package main

import (
    "errors"
    "fmt"
    "math"
)

func circleArea(radius float64) (float64, error) {
    if radius < 0 {
        return 0, errors.New("Area calculation failed, radius is less than zero")}return math.Pi * radius * radius, nil
}

func main(a) {
    radius := 20.0
    area, err := circleArea(radius)
    iferr ! =nil {
        fmt.Println(err)
        return
    }
    fmt.Printf("The Area of circle % 0.2 f", area)
}
Copy the code

Run in playground

In the above program, we check if the radius is less than zero at line 10. If so, 0 is returned along with the corresponding error content. In line 13, if the radius is greater than 0, the area is computed and an error of nil is returned.

In main, we check if error is nil at line 19. If not nil, print an error and return, otherwise print the area of the region.

In this program, the radius is less than zero, so it will print,

Area calculation failed, radius is less than zero
Copy the code

useErrorfforerrorAdd more information

The above program works fine, but it’s a little hard to print the exact error message. This is where the Errorf function of the FMT package comes in handy. This function formats the argument with a string and returns a string as the value of error.

Let’s try Errorf,

package main

import (
    "fmt"
    "math"
)

func circleArea(radius float64) (float64, error) {
    if radius < 0 {
        return 0, fmt.Errorf(Area Calculation failed, RADIUS %0.2f is less than zero", radius)
    }
    return math.Pi * radius * radius, nil
}

func main(a) {
    radius := 20.0
    area, err := circleArea(radius)
    iferr ! =nil {
        fmt.Println(err)
        return
    }
    fmt.Printf("The Area of circle % 0.2 f", area)
}
Copy the code

Run in playground

In the above program, line 10 uses Errorf and prints the actual radius value that caused the error. Running this program prints,

Area calculation failed, radius-20.00 is less than zeroCopy the code

Use structure types and fields to provide informationerrorFor more information on

You can also use the structure type that implements the error interface as error. This gives us more flexibility. In our example, if we want to see errors, the only way is to resolve Area Calculation failed, radius-20.00 is less than zero. This is not an appropriate approach, because if the description changes, our code logic will have to change.

We will use structure fields to provide ERR and RADIUS using the “Assert structure types and get more information from the fields of structure types” approach described in the previous tutorial. We will create a structure type that implements the Error interface and use its fields to provide more information about it.

The first step is to create a structure type to represent error. The naming convention for a type is Error ending. So we’ll name the structure type areaError

type areaError struct {
    err    string
    radius float64
}
Copy the code

The above structure type has a field radius, which stores the radius value responsible for the error, and the Err field, which stores the actual error content.

The next step is to implement the Error interface.

func (e *areaError) Error(a) string {
    return fmt.Sprintf("The radius % 0.2 f: % s", e.radius, e.err)
}
Copy the code

In the code snippet above, we use the pointer receiver * areaError to implement the Error method of the Error interface. This method prints radius and ERR descriptions.

Let’s complete the program by writing the main and circleArea functions.

package main

import (
    "fmt"
    "math"
)

type areaError struct {
    err    string
    radius float64
}

func (e *areaError) Error(a) string {
    return fmt.Sprintf("The radius % 0.2 f: % s", e.radius, e.err)
}

func circleArea(radius float64) (float64, error) {
    if radius < 0 {
        return 0, &areaError{"radius is negative", radius}
    }
    return math.Pi * radius * radius, nil
}

func main(a) {
    radius := 20.0
    area, err := circleArea(radius)
    iferr ! =nil {
        if err, ok := err.(*areaError); ok {
            fmt.Printf("Radius %0.2f is less than zero", err.radius)
            return
        }
        fmt.Println(err)
        return
    }
    fmt.Printf("Area of rectangle1 %0.2f", area)
}
Copy the code

Run in playgroud

In the above program, line 17 circleArea is used to calculate the area of the circle. This function first checks if the radius is less than zero, if so, it creates areaError with the radius of the error and the corresponding error content description, and then returns the address and error content of areaError on line 19. Therefore, we provide more error information, and in this case, the fields with the error structure are customized.

If the radius is not negative, this function computes and returns area and nil

In main on line 26, we try to calculate the area of a circle of radius -20. Since the radius is less than zero, an error is returned.

We check if Err is nil at line 27 and assert that err is of type * areaError in the next line. If the type is * areaError, we use err. Radius on line 29 to get the RADIUS that caused the error, then print the custom error and return it from the program.

The output,

Radius-20.00 is less than zeroCopy the code

Now let’s use the second strategy described in the previous tutorial and use the custom error type method to provide more information about errors.

Methods using structure types are provided abouterrorFor more information on

In this section, we will write a program that calculates a rectangular region. If the length or width is less than 0, the program prints an error.

The first step is to create a structure that represents an error.

type areaError struct {
    err    string //error description
    length float64 //length which caused the error
    width  float64 //width which caused the error
}
Copy the code

The error structure type above contains the error description field err as well as the length length and width.

Now that we have defined the Error type, let’s implement the Error interface and add a few methods to the type to provide more information about it.

func (e *areaError) Error(a) string {
    return e.err
}

func (e *areaError) lengthNegative(a) bool {
    return e.length < 0
}

func (e *areaError) widthNegative(a) bool {
    return e.width < 0
}
Copy the code

In the code snippet above, we return the description of the Error e.err from the Error method. LengthNegative method returns true when Length is less than 0, and widthNegative method returns true when width is less than 0. These two methods provide information, in this case indicating whether the region calculation fails because the length is negative or the width is negative. Therefore, we use methods of the error structure type to provide more information about it.

The next step is to realize the area calculation function.

func rectArea(length, width float64) (float64, error) {
    err := ""
    if length < 0 {
        err += "length is less than zero"
    }
    if width < 0 {
        if err == "" {
            err = "width is less than zero"
        } else {
            err += ", width is less than zero"}}iferr ! ="" {
        return 0, &areaError{err, length, width}
    }
    return length * width, nil
}
Copy the code

The rectArea function above checks if the length or width is less than 0 and returns 0 and error if so, otherwise returns the rectangle area and nil. Let’s complete the program by creating the main function.

func main(a) {
    length, width := 5.0.9.0
    area, err := rectArea(length, width)
    iferr ! =nil {
        if err, ok := err.(*areaError); ok {
            if err.lengthNegative() {
                fmt.Printf("error: length %0.2f is less than zero\n", err.length)

            }
            if err.widthNegative() {
                fmt.Printf("error: width %0.2f is less than zero\n", err.width)

            }
            return
        }
        fmt.Println(err)
        return
    }
    fmt.Println("area of rect", area)
}
Copy the code

In main, we check to see if Err is nil. If it’s not nil, we assert * areaError on the next line. Then use the lengthNegative and widthNegative methods to check whether the error is due to a negative length or width. We print the corresponding error content and return it from the program. Therefore, we use the structure-type approach to provide more information about it.

If there are no errors, the rectangular area is printed.

Finally, a complete program is posted for reference.

package main

import "fmt"

type areaError struct {
    err    string  //error description
    length float64 //length which caused the error
    width  float64 //width which caused the error
}

func (e *areaError) Error(a) string {
    return e.err
}

func (e *areaError) lengthNegative(a) bool {
    return e.length < 0
}

func (e *areaError) widthNegative(a) bool {
    return e.width < 0
}

func rectArea(length, width float64) (float64, error) {
    err := ""
    if length < 0 {
        err += "length is less than zero"
    }
    if width < 0 {
        if err == "" {
            err = "width is less than zero"
        } else {
            err += ", width is less than zero"}}iferr ! ="" {
        return 0, &areaError{err, length, width}
    }
    return length * width, nil
}

func main(a) {
    length, width := 5.0.9.0
    area, err := rectArea(length, width)
    iferr ! =nil {
        if err, ok := err.(*areaError); ok {
            if err.lengthNegative() {
                fmt.Printf("error: length %0.2f is less than zero\n", err.length)

            }
            if err.widthNegative() {
                fmt.Printf("error: width %0.2f is less than zero\n", err.width)

            }
            return
        }
    }
    fmt.Println("area of rect", area)
}
Copy the code

Run in playground

Error: length-5.00 is less than zero Error: width -9.00 is less than zeroCopy the code

We’ve seen examples of two of the three methods described in the error handling tutorial to provide more information.

The third way, using direct comparison, is simpler. I’ll leave it as an exercise for you to figure out how to use this strategy to provide more information about our custom error.