Creating errors in go

Software Engineer with an entrepreneurship gene. Love talking about code, products, UI/UX and how to build startups.
Search for a command to run...

Software Engineer with an entrepreneurship gene. Love talking about code, products, UI/UX and how to build startups.
No comments yet. Be the first to comment.
Goroutines are one of the best features of Go language. It is very easy to run a function as a goroutine, you just have to use the keyword go before the function call. Here is a very simple function which sleeps for n seconds and prints a line. pa...

In a previous post, we saw how we can use Regular expressions in Go to match and replace patterns in a string. While regexps are very useful, you might not want to them for every time you want to do some basic string manipulation. The strings package...

In the previous post, we saw how to write a simple unit test case and also how to write table driven test cases. In this post we will quickly see how to write benchmarks, as go supports running benchmarks on your functions as part of the standard tes...
Go has built-in support for writing test cases for your code. And it is important to write as much test cases as possible to make sure you cover all possible conditions. You have to use the testing standard package to write your unit test cases. Let...
Pro Golang Dev
14 posts
Go is a modern language, but doesn't use try-except blocks to handle errors. Errors are simple values that can be returned from functions and it is common to check for errors before proceeding with your next steps. Let's see the different ways to create your own errors in Go.
Any value is an error if it implements the error interface.
type error interface {
Error() string
}
And any value can implement the error interface, by having a Error method which returns a string. It's as simple as it gets.
The simplest way to create a custom error is to use the New function of the errors package.
Let's look at the New function to understand how it is implemented.
// 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() string {
return e.s
}
This is the entire code for the errors package. We have a errorString struct which has a string field. The Error() function of this struct, just returns back the string field s.
Whenever we use New, we create a new variable of errorString struct and return back the address of the variable.
Now lets look at how we can use errors.New in our own code.
func AreaOfSquare(side float64) (float64, error) {
if side < 0 {
return 0, errors.New("Cannot calculate area which has negative side.")
}
return side * side, nil
}
Now whenever we call AreaOfSquare we will check for the error value and handle it appropriately.
But let's say we need to format the string that we are returning, we can use the fmt.Errorf function to format the string.
Instead of errors.New, we will have to use the fmt.Errorf.
return 0, fmt.Errorf("Cannot calculate area which has %0.2f as side.", side)
String based errors are easy and nice, but if we want the error value to contain even more details about the error that occurec, we should construct our own struct which implements the error interface.
type areaError struct {
err string
side float64
}
func (e *areaError) Error() string {
return fmt.Sprintf("side %0.2f: %s", e.side, e.err)
}
Now, we have our own error type areaError, which has a Error(). But more important is that it has the field side which stores some extra information about your code. You can inspect this error value and handle the error.
With this change, our AreaOfSquare function is
func AreaOfSquare(side float64) (float64, error) {
if side < 0 {
return 0, &areaError{"side is negative", side}
}
return side * side, nil
}
We create a new areaError value, with the error string and the side, which gets returned back to the calling function. We have provided the calling function with more information about the error with our custom error struct.
Let's now write a new package to calculate the area of a rectangle, and we will have it's own areaError struct. There is a slight modification in this struct, as a rectangle can have varying width and height.
type areaError struct {
err string
width float64
height float64
}
func (e *areaError) Error() string {
return e.err
}
func (e *areaError) heightNegative() bool {
return e.height < 0
}
func (e *areaError) widthNegative() bool {
return e.width < 0
}
There are two extra methods to the areaError, which return a boolean saying if the height or widgh is negative. These two methods provide more information about the error, whether the area calculation failed because of the height being negative or width being negative.
Let's now write our AreaOfRect function which uses this areaError struct.
func AreaOfRect(height, width float64) (float64, error) {
errString := ""
if height < 0 {
errString += "height is less than zero"
}
if width < 0 {
if errString == "" {
errString = "width is less than zero"
} else {
errString += ", width is less than zero"
}
}
if errString != "" {
return 0, &areaError{errString, height, width}
}
return height * width, nil
}
In this function, we construct a error string based on which value was negative and use that to create a new areaError value.
Our main function can use this function, and if it gets back any error, it will use the two methods heightNegative() and widthNegative() to handle the error correctly.
func main() {
height, width := -15.0, -2.0
area, err := AreaOfRect(height, width)
if err != nil {
if err, ok := err.(*areaError); ok {
if err.heightNegative() {
fmt.Printf("error: height %0.2f is less than zero\n", err.height)
}
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)
}
In this function, AreaOfRect returns only areaError type and so, we check for the error type by using the err, ok := err.(*areaError) line. But if we have a function which can return multiple different error types, we can use a switch-case statement like this.
if err := FunctionToCall(); err != nil {
switch e := err.(type) {
case *CustomError1:
// handle CustomError1
case *CustomError2:
// handle CustomError2
default:
log.Println(e)
}
}
This article has just scratched the surface of how error values in go can be used effectively. Rob Pike's blog post in the official Go Blog walks you through other patterns in handling errors.