All programming languages have a flow of execution. Commonly, the execution goes line by line. However, you can add control statements and create an alternative flow (using the if statement) or loop some lines (with the for statement). You can also add statements like switch or goto to that list. It stops working when the programming language tries to process abnormal conditions.
Incorrect data types, bad input signals, unobserved code errors, or corrupted files — all can cause an interruption of the flow. In such cases, running the program usually stops with an error. However, Golang has statements that can process unexpected cases behavior and return the control of the flow or help to end the program correctly. Welcome to the new topic about Golang control statements.
Panic
The Go compiler always checks for errors during the building process (before you run your program). It catches common types of errors: syntax errors, unclosed brackets, unused variables, type errors, and more. Despite that, the code runtime is a dynamic environment, and some errors can occur during execution of your code.
Sometimes, you can get corrupted data from the input. You can pass an invalid variable pointer, get out of the range of a slice (or an array), or miss the nil data. These are examples of abnormal conditions during runtime. Remember: the more complex your code becomes, the higher the chances to run into errors are.
Now let's take a look at the output of the following Go program, where we try to pass 1 and 0 as values:
package main
import "fmt"
func main() {
var num1, num2 int
fmt.Scan(&num1, &num2)
fmt.Println(num1 / num2)
}
// Input:
// 1 0
// Output:
// panic: runtime error: integer divide by zeroThe above program returns the result of division of two numbers. However, since it is not possible to divide an integer number by zero, the program starts an event called panic. When the code starts panicking, the normal execution flow of the program stops, and it prints an error message. In simple terms, the panic event happens when something goes unexpectedly wrong during execution.
You can also initiate the panic event directly. With the panic() function, you can terminate the execution by yourself. The panic() function can take any data type as an argument and then print it to the output:
package main
import "fmt"
func main() {
panic("Something has gone wrong!")
fmt.Println("Not printed because of the panic")
}
// Output:
// panic: Something has gone wrong!The program doesn't execute the lines of code below the panic. This makes sense because the execution flow is terminated. However, see what happens if you have the defer statement before the panic():
package main
import "fmt"
func main() {
defer fmt.Println("Will be printed anyway!")
panic("Something has gone wrong!")
}
// Output:
// Will be printed anyway!
// panic: Something has gone wrong!The defer statement works! The panic() function has no effect on the list of deferred calls (if you declare it before the panic).
Recover
A panic is an emergency end of the program. An unexpected end can lead to corrupted files or databases. Naturally, it's not great when your program starts panicking as part of the data processing server. You can't prevent the panic, but you can reduce the possible damage (close the file, break the connection to the database, switch to an alternative data handler).
At the end of the previous section, you saw that panic doesn't cancel the defer statement. To process the panic, you need to get the signal about it inside the defer scope. Let's return to the first example of the panic section. The code below catches the zero divide condition and prints a custom message about it.
package main
import "fmt"
func main() {
var num1, num2 int
fmt.Scan(&num1, &num2)
defer func() {
onPanic := recover() // catch the panic
if onPanic != nil {
fmt.Printf("%d and %d are unacceptable for integer division\n", num1, num2)
}
}()
fmt.Println(num1 / num2)
}
// Input:
// 1 0
// Output:
// 1 and 0 are unacceptable for integer divisionTake notice that the defer statement goes after the fmt.Scan(&num1, &num2) line. This is necessary to pass the scanned values of num1 and num2 to the fmt.Printf() statement of the deferred anonymous function.
The program still panics, but now you control the process! Now you get a meaningful error message. Also, you can log this error message to a file, for example. Or you can handle the error and try to execute the program again. The method of ending the program after a serious error during runtime is called a Graceful exit.
You can benefit from a graceful exit after a panic in Go by using the recover() function. The recover() function gets the signal of the panic and returns the information about the error that occurred during runtime.
Best practices
One of the best practices with panic() in Go is to not use it with production code. The panic() function is not an appropriate error report tool. On many occasions, it is just used as a debug tool. A panic event is more like an exception that your program calls if it faces an abnormal error situation. Therefore, when you write the code, leave the panic events to the runtime. Instead of making the program panic, it is better to return an appropriate error message (don't panic, return an error!).
The example with the division of two numbers is just a demonstration of the recovery process. It is important to say that the use of that pattern is justified in cases when you use external modules or someone else's code. However, if you write code yourself, you should know how it works. You should also be able to predict the cases of abnormal conditions and handle them. Since division by zero is a common error, you could handle the code from the previous example without recover():
package main
import "fmt"
func main() {
var num1, num2 int
fmt.Scan(&num1, &num2)
if num2 == 0 {
fmt.Printf("%d and %d are unacceptable for integer division\n", num1, num2)
} else {
fmt.Println(num1 / num2)
}
}
// Input:
// 1 0
// Output:
// 1 and 0 are unacceptable for integer divisionSoon you will get acquainted with goroutines. It is a complex topic, but now, you should learn something about the features of the recover process in goroutines. If the goroutine starts panicking, the recover() function must be defined in the same goroutine. If you try to catch the goroutine panic from the main scope or another goroutine, your program will crash.
Conclusion
A panic is an event that you don't expect. Try to minimize cases that can cause panic events in your code. Checking for a nil data is suitable in most cases. If you are working with numbers, remember to add error checking for 0 values. Don't use strict comparison for bounds of the range: try to replace a == statement with a >= or <=. When working with strings, check the incoming string for unsuitable symbols: create a set of characters to use, such as a regexp, for example. These simple rules will make your code more stable.
Now, here's a recap of what this topic has covered:
a panic is an event caused by abnormal conditions during runtime;
you can use the
panic()function to call the panic event directly;recover is the process that can handle a certain panic event.