How to use error control flows in Golang

Error control flows in Golang

In this article, we'll learn about the usage of error handling statements defer, panic, and recover in Golang with the help of examples. We use these statements to manage the control flow in the programs.

Multiple ways to control flow in Golang

Defer statement

We use the defer statement to perform various clean-up actions. For example, we use it to close the file, clear DB or HTTP connections, and so on. If there are multiple defer calls inside a function, defer statements are run in the Last in First Out (LIFO) order after the surrounding function returns.

Example

Let’s look at a simple function that opens the src file and creates a dest file. The function aims to copy the contents of src to dest.

Code

package main
import (
"fmt"
"io"
"os"
)
func copy(src, dest string) (result int64, err error) {
srcContent, err := os.Open(src)
if err != nil {
return 0, err
}
dstContent, err := os.Create(dest)
if err != nil {
return 0, err
}
result, err = io.Copy(dstContent, srcContent)
dstContent.Close()
srcContent.Close()
return result, err
}
func main() {
fmt.Printf("copy files without defer")
}

Explanation

  • Line 10: Open the src file and store the content.
  • Line 15: Create dest file to copy the content from src.
  • Line 20: Copy the contents of src file to dest file.
  • Line 21 & 22: Close the open file connections.
  • Line 12 & 17: Return with an error when file handling functions error out.

In an idle scenario the code above works fine; but, there is a minor bug where the io.Create() function fails, and program exits without closing the open file connection on src.

Code

package main
import (
"fmt"
"io"
"os"
)
func copy(src, dest string) (int64, error) {
srcContent, err := os.Open(src)
if err != nil {
return 0, err
}
defer srcContent.Close()
dstContent, err := os.Create(dest)
if err != nil {
return 0, err
}
defer dstContent.Close()
val, err := io.Copy(dstContent, srcContent)
return val, nil
}
func main() {
fmt.Printf("Copy Files with defer")
}

Explanation

  • Line 8: Open the src file and store the content.
  • Line 16: Create dest file to copy the content from src.
  • Line 21: Copy the contents of src file to dest file.
  • Line 14 and 20: Use defer to cleanup the open file connections.
  • Line 12 and 18: Return with an error when file handling functions error out.

We use the defer statement to close the file’s open connections if the program exits (equivalent to finally in Ruby, and try-with-resources in Java).

Panic statement

Panic is a function that stalls the ordinary flow of control (equivalent to raise in Ruby, and throw in JS). The panic statement executes after a deferred statement. The following code example displays output three before the panic statement.

package main
import "fmt"
func main(){
fmt.Println("one")
defer fmt.Println("three")
panic("a panic happened")
fmt.Println("two")
}
// Output:
// one
// three
// panic: a panic happened

Explanation

  • Line 4: Use theprint statement before panic.
  • Line 5: Use thedefer statement.
  • Line 6: Raise a panic.
  • Line 7: Unreachable print statement.
  • Line 10 and 13: Output of the program.

Here's another example of how a panic is raised at runtime in a program.

package main
import "fmt"
func main() {
x := 20
y := 0
fmt.Println(x/y)
}
// panic: runtime error: integer divide by zero
// goroutine 1 [running]:
// main.main()
// /tmp/sandbox064477089/prog.go:6 +0x11

Recover statement

Recover helps us take control when a block of goroutine panics. Recover is only useful with deferred functions. If an application panics, it no longer executes the rest of the program except for deferred functions.

During normal execution, a call to recover will return nil and have no other effect. If the current goroutine panic's, a call to recover will capture the value given to panic and resume normal execution.

package main
import "fmt"
func main() {
x := 0
y := 20
divide(x, y)
}
func actionDefer(x, y int) {
if r := recover(); r != nil {
fmt.Printf("Recover in divide: %v \n", r)
validOps(x, y)
}
}
func divide(x int, y int) {
defer actionDefer(x, y)
s, d, m := x+y, y/x, x*y
fmt.Printf("sum=%v, divide=%v, multiply=%v \n", s, d, m)
}
func validOps(x int, y int) {
s, m := x+y, y*x
fmt.Printf("sum=%v, multiply=%v \n", s, m)
}

Explanation

  • Line 11: The actionDefer statement has recover logic when a program panics.
  • Line 21: if x == 0 , statement raises a panic.
  • Line 19: As defer statements are run before panic, actionDefer method is executed and recovers from the panic.

Conclusion

In this article, we looked at the defer, recover, and panic functions and their usage in Golang.

Free Resources