Golang defer return error

Being able to defer functions is a powerful feature. But with the Go error handling pattern, it can be easy to ignore errors. Take this for example:

Being able to defer functions is a powerful feature. But with the Go error handling pattern, it can be easy to ignore errors. Take this for example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import "fmt"

func cleanup() error {
        fmt.Println("Running cleanup...")
        return fmt.Errorf("error on cleanup")
}

func getMessage() (string, error) {
        defer cleanup()

        return "hello world", nil
}

func main() {
        message, err := getMessage()
        if err != nil {
                fmt.Printf("Error getting message: %vn", err)
        } else {
                fmt.Printf("Success. Message: '%s'n", message)
        }
}

The output of this application is:

1
2
Running cleanup...
Success. Message: 'hello world'

We’ve completely silenced the error that is returned from cleanup. That’s not good! But how do we handle an error from a deferred function?

We can use a function closure to allow us to handle the error message. Let’s change our getMessage function:

1
2
3
4
5
6
7
func getMessage() (msg string, err error) {
        defer func() {
                err = cleanup()
        }()

        return "hello world", err
}

We define and then invoke the anonymous function as the deferred function itself so that we can encapsulate the error handling of cleanup. Our deferred function references err, which is the variable defined in the function definition (the other modification for this solution). Now we have the desired output:

1
2
Running cleanup...
Error getting message: error on cleanup

The error that cleanup returns is properly passed to the caller.

This is great! But… there’s a small logic bug hiding in this. Let’s expand on this example a little bit. Let’s change cleanup so that it no longer returns an error:

1
2
3
4
func cleanup() error {
        fmt.Println("Running cleanup...")
        return nil
}

And let’s add a new function that does return an error:

1
2
3
func doAnotherThing() error {
        return fmt.Errorf("error from another thing")
}

And adding a call to doAnotherThing from getMessage:

1
2
3
4
5
6
7
8
9
func getMessage() (msg string, err error) {
        defer func() {
                err = cleanup()
        }()

        err = doAnotherThing()

        return "hello world", err
}

So we get our error from doAnotherThing and then we return that error to the caller of getMessage, right? Let’s see:

1
2
Running cleanup...
Success. Message: 'hello world'

We expected the error handling in main, but we did not get an error back from getMessage. That’s because our deferred function ran cleanup and overwrite the error that was returned from doAnotherThing. So we accidentally silenced that error in the normal flow of the function!

The fix is straightforward. In the deferred function, have a temporary error variable and test to see if cleanup returns an error. If yes, then set it. Otherwise do not alter the return error:

1
2
3
4
5
6
7
8
9
10
11
func getMessage() (msg string, err error) {
        defer func() {
                if tempErr := cleanup(); tempErr != nil {
                        err = tempErr
                }
        }()

        err = doAnotherThing()

        return "hello world", err
}

Now we run this and get the expected error:

1
2
Running cleanup...
Error getting message: error from another thing

But we’re not done yet! There’s another consideration: If our deferred function (cleanup in this case) returns an error but other code in the function does too (doAnotherThing here), which error do we want to return? Well… it depends. I think there are a few right answers that the programmer will have to choose from:

  1. Return the error from doAnotherThing and ignore the error from cleanup
  2. Return the error from cleanup and ignore the error from doAnotherThing
  3. Return all of the errors as []error

The first option is usually the most natural. After all, it’s commonly the main component of the function flow that you want to report the error for. The last option to deal with all of the errors is not typical error handling:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
func getMessage() (msg string, errs []error) {
        defer func() {
                if tempErr := cleanup(); tempErr != nil {
                        errs = append(errs, tempErr)
                }
        }()

        if tempErr := doAnotherThing(); tempErr != nil {
                errs = append(errs, tempErr)
                return "", errs
        }

        return "hello world", errs
}

func main() {
        message, errs := getMessage()
        if errs != nil {
                fmt.Printf("There are %d error(s)n", len(errs))
                for _, err := range errs {
                        fmt.Printf("Error: %vn", err)
                }
        } else {
                fmt.Printf("Success. Message: '%s'n", message)
        }
}

But, these are the options! Different scenarios may require different choices for which errors you return.

Hopefully this blog post has showed how you can handle errors from deferred functions, but also make sure you don’t accidentally cause a nasty bug by overwriting other errors!

In this article, we shall be discussing how to return and handle errors effectively using custom and inbuilt Golang functions, with help of practical examples.

Golang return error

An error is basically a fault that occurs in a program execution flow. These errors can be of various natures:- Caused by programmers through code syntax and interface errors , system-related Resources and Runtime errors, algorithm-related logic and arithmetic errors. Which later are solved through debugging process.

In Golang ,The Error is an interface that holds Error() string method. Its implemented as follows

type error interface {
Error() string
}

In an nutshell, when the Error() method is called, it’s return value is in form of string datatype. Through the use of inbuilt Go functions of the fmt and errors packages, we can construct the kind of error message to be displayed. Below is an example to construct Errors using fmt.Error() in Golang, i.e you want to read a file from a given path, unfortunate the file doesn’t exist or the path given is invalid. For example:=

package main

import (
	"fmt"
	"os"
)

func ReadFile(file string) error {
	dataFile, err := os.ReadFile(file)
	if err != nil {
		return fmt.Errorf("An error occurred while Reading the file: open : %v", err)
	}
	fmt.Println(string(dataFile))
	return nil
}
func main() {
	resultsErr := ReadFile("")
	if resultsErr != nil {
		fmt.Printf("%v", resultsErr)
	}
}

Output:

ALSO READ: Golang check if key exists in map [SOLVED]

With the file attached ensure you replace the ReadFile(«test.txt»)

$ go run main.go
Hello

without file attached

$ go run main.go
An error occurred while Reading the file: open: no such file or directory

Explanation:- In the above code, ReadFile() error{} function returns an error which is nil whenever no error encountered. In Golang, the Error return value is nil as the default, or “zero”. Notice that checking if err != nil{} is the idiomatic way to determine if an error was encountered in Golang syntax, in this function we are returning the error only, handling the file data within the function. fmt.Error() enables us to customize the kind of message to be displayed. These messages are always in a lowercase format and don’t end with punctuation.

In Golang there are numerous ways to return and handle errors Namely:=

  • Casting Errors
  • Error wrapping mechanism
  • Panic, defer and recover

Different methods of error handling in Go Func

Method 1:- Casting Errors

Casting errors is a way of defining custom and expected errors, with golang we can make use of erros.Isand errors.As() error functions to cast different types of errors. i.e,errors.Is we create a custom error of a particular type and check If the error matches the specific type the function will return true, if not it will return false.

package main

import (
	"errors"
	"fmt"
	"io/fs"
	"os"
)

var fileNotFound = errors.New("The file doesn't  exist")

func ReadFile(file string) error {
	dataFile, readErr := os.ReadFile(file)
	if readErr != nil {
		if errors.Is(readErr, fs.ErrNotExist) {
			return fmt.Errorf("this fileName %s  doesn't exist ", file)
		} else {
			return fmt.Errorf("Error  occured while opening the file : %w", readErr)
		}
	}
	fmt.Println(string(dataFile))
	return nil
}
func main() {
	fileName := os.Args[1]
	if fileName != "" {
		resultsError := ReadFile(fileName)
		if resultsError != nil {
			fmt.Printf("%v", resultsError)
		}
	} else {
		fmt.Println("the file name cant be empty")
	}
}

Output:

$ go run main.go "new"
this fileName new  doesn't exist

Explanation:- We are using errors.Is(readErr, fs.ErrNotExist) {} to check if the file passed exist, if it doesn’t exist we return custom message as shown above. we can also use the custom error message such as errors.New() to create expected error and handle it as errors.Is(readErr, fileNotFound) {} the return values will be the same.

ALSO READ: Golang Print Struct Variables [SOLVED]

Method 2:- Error wrapping

Wrapping is a way of using other errors within a function to provide more context and detailed error messages.

fmt.Error() function enable us to create a wrapped errors with use of %w flag. The %w flag is used for inspecting and unwrapping errors.
In this subtitles we can incorporate other functions from errors package used to handle errors, namely:- errors.As, errors.Is, errors.Unwrap functions. errors.As is used to cast a specific error type, i.e func As(err error, target any) bool{}, also, the errors.Unwrap is used to inspect and expose the underlying errors in a program,i.e func (e *PathError)Unwrap()error{ return e.Err}, Furthermore the errors.Is mostly for comparing error value against the sentinel value if is true or false, i.e func Is(err,target error) bool{}.

Example of Error Wrapping

package main

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

func ReadFile(file string) error {
	dataFile, readErr := os.ReadFile(file)
	var pathError *os.PathError
	if readErr != nil {
		if errors.As(readErr, &pathError) {
			return fmt.Errorf("this fileName %s  doesn't exist and failed  opening file at this path %v", file, pathError.Path)
		}
		return fmt.Errorf("Error  occured while opening the file : %w", readErr)

	}
	fmt.Println(string(dataFile))
	return nil
}
func main() {
	fileName := os.Args[1]
	if fileName != "" {
		resultsError := ReadFile(fileName)
		if resultsError != nil {
			fmt.Printf("%v", resultsError)
		}
	} else {
		fmt.Println("the file name can't be empty")
	}
}

Output:

With the file attached ensure you replace the ReadFile(«test.txt»)

$ go run main.go
Hello

without file attached

$ go run main.go ""
the file name can't be empty
$ go run main.go next.txt
this fileName news.txt  doesn't exist and failed opening file at this path news.txt

Explanation:- In the above code we have used fmt.Errorf() functions to format the error message to be displayed and wrapping error using a custom error message with wrap function errors.Is() which checks if the path exists. You can avoid unnecessary error wrapping and handle it once.

ALSO READ: Golang SQLite3 Tutorial [With Examples]

Method-3: Using Panic, Defer and Recover

We have covered this topic in detail in a separate article Golang panic handing [capture, defer, recover, log]

Summary

At this point in this article, you have learned various ways to return and handle errors in the Golang function. In Go, Errors are considered to be a very lightweight piece of data that implements the Error interface. Custom errors in Go help in debugging and signaling where the error has occurred from. Error tracing is easy as compared to other programming languages. Golang application development, error handling is very critical and helps one not only during debugging but also to monitor the application behavior. We recommend you to read more about panic, recover and defer mechanism of error handling as well.

References

error-handling in Go
Working with errors in golang
Errors

defer is one of those interesting features of Go that doesn’t really have a direct equivalent in other languages. Its behaviour can be a little confusing at first, but once you understand how to use defer, it’s really neat—and we use it all the time in Go programs.

Let’s talk about defer, then. First, what is the problem that it even solves?

It often happens that we do something in a Go function which requires some kind of cleanup before the function exits. For example, whenever we open a file, we must also close it before exit, or that resource will leak (that is, it will hang around taking up memory, in principle forever). The same applies to many other kinds of resources, such as HTTP response bodies or network connections.

So how can we clean up such a resource? We can explicitly call Close on it, like this:

f, err := os.Open("testdata/somefile.txt")
... // do something with f
f.Close()

Fine! The resource is cleaned up. But a function can exit at any point by using return, not just at the end. This creates a potential problem, doesn’t it? What if we exit the function before f.Close() is called? That would mean f is never closed, and it will hang around in memory forever.

For simple, one-shot programs like CLI tools, that’s maybe not so serious, since the program will exit soon anyway. But for long-running programs like servers, leakage means that the program’s resource usage keeps growing, until eventually it crashes.

If we’re diligent and disciplined programmers, which no doubt we are, we can just try to remember to call f.Close() at every place the function can return. But nobody’s perfect, and if someone else were to make changes to this function, they might not even be aware that they were supposed to close f before exiting. If there are several resources to clean up, this could also lead to a lot of duplicated code.

What we really want is some way to say “When this function is about to exit, no matter where or how, make sure f is closed”.

It turns out that Go’s defer keyword is exactly what we’re looking for. A defer statement takes some function call (for example f.Close()) and defers it.

That is, it doesn’t call the function now, but it “remembers” it for later. And when the function is about to exit for whatever reason, that’s when the deferred function call actually happens.

Here’s what that might look like with our file example:

f, err := os.Open("testdata/somefile.txt")
if err != nil {
    return err
}
defer f.Close()
... // do stuff with f

What’s happening here? First, we try to obtain f by calling os.Open. This may fail, in which case we don’t need to worry about closing the file, because we couldn’t open it! So we return an error in that case.

But now we know that we successfully opened f, so we immediately defer f.Close(). This doesn’t close f, but it “remembers” that we need f.Close() to be called on exit.

And now we can do anything we want in the rest of the function, safe in the knowledge that whatever happens, f will be automatically closed, without us having to worry about it. That’s what defer does. It’s pretty cool.

Multiple defers

It’s not common, but it sometimes happens that we need to defer more than one function call. For example, perhaps we open two files, and we want to defer closing each one separately.

This is fine, and we can use the defer keyword as many times as we like in a function, and all the deferred calls will be executed when the function exits. It’s worth knowing that the calls are executed in reverse order: that is to say, last deferred, first run. This is sometimes referred to as stacking defers.

defer cleanup1() // executed last on exit
defer cleanup2() // executed first on exit

Named result parameters

Recall from the previous chapter that when we’re declaring a function’s result list, we can give those results names, or not. (Usually we don’t need to.) When and why would we want to give them names, then?

One very important reason is documentation. After all, source code is written for humans, not computers. If it’s not apparent from a function signature what its results represent, giving them names can help. Take our example from earlier:

func location() (float64, float64, error) {

This function presumably gets your location, if the name is anything to go by, and returns two float64 results (and maybe an error). But what are those two results? We can make that clearer by giving them names:

func location() (latitude float64, longitude float64, error) {

Now the reader can see that the first value is the latitude co-ordinate (in some agreed co-ordinate system) and the second the longitude. We might have guessed that this was the case, but it’s nice to see it written down explicitly.

We don’t need to do anything special in the function, just because its results are named; we continue to return them as we would any other results:

But there’s one handy side-effect; the result parameters are now available inside the function for us to refer to, just as though they were input parameters or local variables. So we could assign to them, for example:

latitude = 50.5897
longitude = -4.6036
return latitude, longitude

Naked returns considered harmful

The Go specification actually allows us to omit the names from the return statement in this case, and this would implicitly return whatever the values of latitude and longitude happen to be at this point. But even though that’s legal syntax, it’s not good practice.

It’s always clearer to specify the exact values or variables you’re returning, and there’s no benefit to omitting them. So you should avoid writing these so-called naked returns, even though you’ll sometimes see them in other people’s code.

In particular, you should be aware that just because a function has named result parameters, that doesn’t mean you must write a naked return. You can, and should, make your return values explicit.

Modifying result parameters after exit

So defer is useful for cleaning up resources before the function exits. Great. But what if this cleanup operation needs to change the function’s results?

For example, suppose we have some function which writes data to a file, and returns an error result to indicate whether it succeeded.

Naturally, we want to defer closing the file, to avoid leaking it. But that close operation itself could fail, and it’s quite likely that that mean we’ve lost the user’s data. Suppose we ran out of disk space just before we wrote the last byte, for example, and on trying to close the file we get some error.

What to do? Our function must return the error, in order to let the user know that we lost their data. But the error doesn’t happen until the deferred call to f.Close(), and at that point the function’s result value is already set. Consider this (partial) code:

... // we successfully opened f
defer f.Close()
... // write data to f
... // everything good, so return nil error
return nil

If the call to f.Close() happens to fail, returning some error, there’s nothing we can do about it, because the function is going to return nil no matter what happens. It’s baked into the return statement. Or is it?

Deferring a function literal

Because we can defer any function call, we don’t have to defer just f.Close(). Instead, we could write some function literal that calls f.Close(), and defer that:

defer func() {
    closeErr = f.Close()
    if closeErr != nil {
        fmt.Println("oh no")
    }
}()

(Note the empty parentheses at the end, after the function literal’s closing curly brace. We don’t defer a function, remember, but a function call, so having defined the anonymous function we want to defer, we then add the parentheses to call it.)

This is a significant improvement: we can catch the error returned by f.Close(), and if we can’t change the result returned by the function, we can at least bewail the situation by printing "oh no". That’s something, at least.

Deferring a closure

But we can do even better! If we named this function’s error result parameter (let’s call it err, to be conventional), then it’s available for setting inside the function. We can assign to err anywhere in the function, and that will be the value that the function ultimately returns.

How does that help in our deferred function literal? Well, a Go function literal is a closure: it can access all the variables in the closing scope—including err. Because it’s a closure on err, it can modify err before the function actually returns.

So we can opt to overwrite the value of err with whatever closeErr happens to be:

defer func() {
    closeErr = f.Close()
    if closeErr != nil {
        err = closeErr
    }
}()

We achieved the seemingly impossible: modifying the function’s result after it exited (but before it returned). All it took was a combination of named result parameters, defer, and a closure.

Note that it wouldn’t have been correct to simply set err to whatever f.Close() returned:

defer func() {
    err = f.Close() // this is wrong
}

Why not? Because err already has some value, specified by the function’s return statement. We don’t want to erase that and overwrite it with, for example, nil, if the close operation succeeds. The only time we want to overwrite err is if f.Close() fails.

You won’t need to do anything quite as tricky as this in most functions, but it’s occasionally extremely useful.

In practice, a better pattern to use when dealing with writable files is to call f.Sync when you’re finished writing. This tells the operating system to flush all unsaved data to disk. Then it’s safe to simply defer f.Close(), ignoring any error:

f, err := os.Create("testdata/somefile.txt")
... // handle error
defer f.Close()
... // write data to f
return f.Sync()

If f.Sync doesn’t succeed, then at least we returned that error back to our caller, and we also avoided leaking f through the deferred call to f.Close. This is shorter, clearer, and less magical than deferring a closure over named result parameters, so I prefer it.

Takeaways

  • The defer keyword in Go lets us defer execution of a block of code until the current function exits.

  • This is helpful when, for example, closing opened files or otherwise cleaning up resources that could leak. Stacked defers run in reverse order.

  • You can name a function’s result parameters, and the main value of this is to document what those results mean.

  • Naked returns (bare return statement with no arguments) are legal syntactically, and you’ll even see them used in some old blog posts and tutorials. But there’s no good reason to use them, and it’s always clearer to return some explicit value, such as nil.

  • When a function has named results, you can modify them in a deferred closure: for example, to return some error encountered when trying to clean up resources.

Понравилась статья? Поделить с друзьями:
  • Golang custom error type
  • Golang create new error
  • Golang catch error
  • Going into standby mode как исправить
  • Gog ошибка 0xc000007b