Basics of error handling in Go

Jawad Ahmad
5 min readSep 30, 2018

--

No matter how great we are at programming, sometimes our programs have errors. They may occur because of our mistakes, an unexpected user input, an erroneous server response and for a thousand of other reasons.

A common and a huge mistake that we as developer do while programming is to take errors for granted. We consider handling them as a low priority feature that can be implemented later . We so much rely on the linters these days to spot out errors in our programs and we possibly do not cover all flows of the application while testing the application in our testing environment in which linters could spot things out for us.

And when we deploy our application in production, a moment later, it crashes with the huge stack trace. We immediately start looking at the code and start debugging it, after few hours of debugging, you find out that you forget to close the opened file or you forget to handle a failure in network call or even forget to handle JSON or Date parsing failure from a string at some place in your code. This is so normal for even an experienced developer to forget things like these in the code.

Traditional way of handling errors in popular programming languages such as C++, Java, C#, Javascript and Python etc use the try catch block. It is considered as an optional feature. The language itself do not enforce you to handle errors. The traditional try catch works this way.

Tradition way of handling errors.
try {
// code...
} catch (err) {
// error handling
}

Go took different approach to handle errors, it enforces you to handle these errors so you won’t forget. Although it makes your code little arguably difficult to read for some people but it makes your code robust and you might get little or no surprises at all when you run the code in production.

Today, we are going to take a look at handling errors in Go and how it enforces you to handle these errors in your main flow of the program so you won’t get any surprises when you run your application in production.

NOTE: This article does not teach you the best practices to handle errors in Go.

Errors in Go are just simple values. You can assign them to variables, pass to functions and even return from the functions. You can simply create them using the errors package which happens to be builtin package. Yay!

package main

import (
"errors"
"fmt"
)

func main(){
// Creating and assinging error value
myCustomError := errors.New("custom error")
myCustomError2 := ThisFunctionReturnError()
//passing error values to functions
ThisFunctionGetsErrorValueAsAParameter(myCustomError)
ThisFunctionGetsErrorValueAsAParameter(myCustomError2)
}

func ThisFunctionReturnError() error {
return errors.New("custom error 2")
}
// Please never make your function name big, this is just for demonstration purpose.
func ThisFunctionGetsErrorValueAsAParameter(err error){
fmt.Println(err.Error())
}
//output
custom error
custom error 2

Note: error is an interface type, which means values of error type would be of reference type that can be nil when no value is there. It has only one method which returns the error string.

type error interface {
Error() string
}

So, to check for error is pretty simple. We just need to check for nil, if the value is nil then this means there is no error and vice versa.

package main

import (
"errors"
"fmt"
)


func main(){
// Creating and assinging error value
myCustomError := errors.New("custom error")
if myCustomError != nil {
fmt.Println(myCustomError.Error())
}
myCustomError = nil
if myCustomError != nil {
fmt.Println("Error is still there")
}else{
fmt.Println("Woho, error has gone!")
}
}
//output
custom error
Woho, error has gone!

That is all you need to know about creating and consuming errors for now. let us have a look at some real use cases.

A simple network call failure example:-

import (
"errors"
"fmt"
"http"
)

func main(){
response, err := http.Get("http://golang.org/")
if err != nil {
// handle network call failure
}
// do something with the response.
}

You can even return this error value from a function making network call.

import (
"errors"
"fmt"
"os"
"http"
)

func main(){
data, err := makeNetworkCall("http://golang.org/")
if err != nil{
fmt.Println(err.Error())
os.Exit(0) // please do not do this is real case
// you can also use log.Fatal(err.Error())
}
// parse or do whatever with your response
}
func makeNetworkCall(url) ([]byte, error) {
response, err := http.Get(url)
if err != nil {
return nil, err
}
defer response.Body.Close()
dataInBytes := parseBody(response.Body)
// parse body is dummy function, it does not exists in any lib.
// if all goes well

return dataInBytes, nil
// remember you can assign a nil value to error type because it is of reference type.
}

Similarly, all the builtin libraries return errors when their functions possibly can get fail. So the caller of function know that the respective function can fail and handle the returning error appropriately.

Few more examples :-

Opening a file

f, err := os.Open("filename.ext")
if err != nil {
log.Fatal(err)
}
// do something with the open *File f
if err := f.Close(); err != nil {
return err
}
// even closing a file can fail so f.close() also returns an error

Marshing and Unmarshling JSON

type Message struct {
Name string
Body string
}
m := Message{"Hello", "Emumba"}
b, err := json.Marshal(m)if err != nil {
log.Fatal(err)
}
........var m Message
b :=[]byte(`{"Name":"Hello","Body":"Emumba"}`)
err := json.Unmarshal(b, &m)
if err != nil {
log.Fatal(err)
}

etc. There are plenty of more examples out there.

That is all for this article, it doesn’t cover all about the error handling in Go. There are plenty of more things that need to be cover but these are the basics of error handling in Go for a starter.

I hope this article is useful to you. Please let me know if you have any comments or suggestions. Thank you!

--

--