Panic, Defer, and Recover
Part of the Object Oriented Programming section of Coddy's GO journey — lesson 57 of 107.
While Go emphasizes returning errors, some situations are truly unrecoverable. For these cases, Go provides panic, defer, and recover—a mechanism for handling exceptional circumstances.
panic stops normal execution immediately. It's reserved for programming errors like accessing an out-of-bounds index, not for expected errors like invalid user input:
func MustGetConfig(key string) string {
value, exists := config[key]
if !exists {
panic("missing required config: " + key)
}
return value
}defer schedules a function to run when the surrounding function returns—whether normally or due to a panic. Deferred calls execute in reverse order (last-in, first-out):
func ProcessFile() {
fmt.Println("Opening file")
defer fmt.Println("Closing file")
fmt.Println("Processing...")
// Output: Opening file, Processing..., Closing file
}recover catches a panic and returns normal execution. It only works inside a deferred function:
func SafeOperation() (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recovered from panic: %v", r)
}
}()
riskyOperation()
return nil
}This pattern converts a panic into a regular error, allowing your program to continue. Use it sparingly—prefer returning errors for expected failure cases, and reserve panic/recover for truly exceptional situations like corrupted state or unrecoverable conditions.
Challenge
EasyLet's build a safe division calculator that demonstrates how to use panic, defer, and recover to handle exceptional situations gracefully. Your calculator will attempt risky operations and convert panics into regular errors that can be handled normally.
You'll organize your code across two files:
calculator.go: Create the division logic with panic recovery.Implement a
Dividefunction that takes two integers and panics with the message"division by zero"if the divisor is zero. Otherwise, it returns the integer result of the division.Implement a
SafeDividefunction that takes two integers and returns both an integer result and an error. This function should:- Use
deferwith an anonymous function to recover from any panic - If a panic is recovered, convert it to an error with the format
"calculation error: [panic message]" - Call the
Dividefunction internally - Return the result and
nilif successful, or zero and the error if a panic was recovered
- Use
main.go: Read two integers from input and useSafeDivideto perform the calculation safely. Also demonstratedeferby printing cleanup messages in the correct order.Your main function should:
- Print
Starting calculationat the beginning - Use
deferto schedule printingCleanup complete - Use another
deferto schedule printingReleasing resources - Call
SafeDividewith the input values - Print either
Result: [value]orError: [error message]based on the outcome
- Print
The following inputs will be provided:
- Line 1: First integer (dividend)
- Line 2: Second integer (divisor)
Remember that deferred calls execute in reverse order (last-in, first-out), so your cleanup messages should appear in the opposite order from how they were deferred.
For example, given 20 and 4, your output should be:
Starting calculation
Result: 5
Releasing resources
Cleanup completeAnd given 10 and 0, your output should be:
Starting calculation
Error: calculation error: division by zero
Releasing resources
Cleanup completeCheat sheet
Go provides panic, defer, and recover for handling exceptional circumstances.
panic stops normal execution immediately and is reserved for unrecoverable programming errors:
func MustGetConfig(key string) string {
value, exists := config[key]
if !exists {
panic("missing required config: " + key)
}
return value
}defer schedules a function to run when the surrounding function returns. Deferred calls execute in reverse order (last-in, first-out):
func ProcessFile() {
fmt.Println("Opening file")
defer fmt.Println("Closing file")
fmt.Println("Processing...")
// Output: Opening file, Processing..., Closing file
}recover catches a panic and returns normal execution. It only works inside a deferred function:
func SafeOperation() (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recovered from panic: %v", r)
}
}()
riskyOperation()
return nil
}This pattern converts a panic into a regular error. Prefer returning errors for expected failures, and reserve panic/recover for truly exceptional situations.
Try it yourself
package main
import (
"fmt"
)
func main() {
// Read input
var dividend, divisor int
fmt.Scanln(÷nd)
fmt.Scanln(&divisor)
// Print starting message
fmt.Println("Starting calculation")
// TODO: Use defer to schedule "Cleanup complete" message
// TODO: Use defer to schedule "Releasing resources" message
// Remember: deferred calls execute in LIFO order (last-in, first-out)
// TODO: Call SafeDivide with the input values
// TODO: Print either "Result: [value]" or "Error: [error message]"
// based on whether an error was returned
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of Go OOP
External FilesGo Workspace & ModulesPackages & ImportsExported vs Unexported NamesIntroduction to OOP in GoStructs as ClassesDefining Methods on StructsPointer vs Value ReceiversStruct InitializationConstructor FunctionsRecap - Simple Calculator4Interfaces
Introduction to InterfacesImplicit ImplementationInterface as ContractEmpty Interface (any)Type AssertionType SwitchInterface CompositionStringer & Error InterfacesRecap - Shape Calculator7Encapsulation
Exported vs Unexported FieldsPackage-Level EncapsulationGetter & Setter MethodsInformation Hiding in GoRecap - Student Records10Generics (Go 1.18+)
Introduction to GenericsType ParametersType ConstraintsGeneric StructsGeneric Methods WorkaroundRecap - Generic Collection2Types & Structs Deep Dive
Basic & Composite TypesCustom Type DefinitionsStruct TagsAnonymous StructsNested StructsZero Values & DefaultsRecap - Contact Book5Composition Over Inheritance
Why Go Has No InheritanceStruct Embedding BasicsMethod PromotionEmbedding Multiple StructsEmbedding vs AggregationShadowing Embedded MethodsRecap - Employee Hierarchy8Error Handling & OOP
The error InterfaceCustom Error TypesError Wrapping (fmt.Errorf)Sentinel Errorserrors.Is() and errors.As()Panic, Defer, and RecoverRecap - File Parser3Pointers & Memory
Pointer Basics in GoPointers to StructsPass by Value vs ReferenceThe new() FunctionGarbage Collection in GoRecap - Linked List Builder6Polymorphism in Go
Polymorphism via InterfacesDuck Typing in GoInterface Satisfaction RulesPolymorphic CollectionsDependency InjectionRecap - Payment Processor9Concurrency & OOP
Goroutines BasicsChannels & CommunicationBuffered vs Unbuffered ChanSelect Statementsync.Mutex & sync.RWMutexsync.WaitGroupThread-Safe Struct DesignRecap - Worker Pool