Mastering Go Error Handling: From Errors to Panic and Recover
This article explains Go's error handling conventions, including returning error values, creating custom errors with the errors package, using fmt.Errorf, handling runtime panics, and recovering from them with defer, plus advanced patterns like panic‑recover wrappers and closure‑based error management, illustrated with comprehensive code examples.
Error
Returning an error object as the sole or last return value—if the value is nil, no error occurred—and the calling function must always check the received error.
Handle errors and return error information to the user; panic and recover are used for true exceptions.
To prevent a function (or the whole program) from being aborted when an error occurs, the error must be checked after calling the function.
if value, err := pack1.Func1(param1); err != nil {
fmt.Printf("Error %s in pack1.Func1 with parameter %v", err.Error(), param1)
// return or return err
}
// Process(value)Error Handling
Go has a predefined error interface type.
type error interface {
Error() string
}Error values represent abnormal states; the program can terminate with os.Exit(1) when in an error state.
Defining Errors
Whenever a new error type is needed, use errors.New from the errors package to create one with an appropriate message.
// errors.go
package main
import (
"errors"
"fmt"
)
var errNotFound error = errors.New("Not found error")
func main() {
fmt.Printf("error: %v", errNotFound)
}
// output: error: Not found errorUse it in a square‑root function example:
func Sqrt(f float64) (float64, error) {
if f < 0 {
return 0, errors.New("math - square root of negative number")
}
// implementation of Sqrt
}
if f, err := Sqrt(-1); err != nil {
fmt.Printf("Error:%s
", err)
}Create richer messages with fmt.Errorf:
if f < 0 {
return 0, fmt.Errorf("math: square root of negative number %g", f)
}Command‑line help error example:
if len(os.Args) > 1 && (os.Args[1] == "-h" || os.Args[1] == "--help") {
err = fmt.Errorf("usage: %s infile.txt outfile.txt", filepath.Base(os.Args[0]))
return
}Runtime Panic
Runtime panics occur on out‑of‑bounds array access, failed type assertions, etc., producing a runtime.Error value with a RuntimeError() method. panic can be invoked directly for unrecoverable conditions; it prints the provided value and aborts the program.
package main
import "fmt"
func main() {
fmt.Println("Starting the program")
panic("A severe error occurred: stopping the program!")
fmt.Println("Ending the program")
}Output shows the panic stack trace.
Recover from Panic
recoveris used inside a defer function to capture a panic value; it returns nil if no panic occurred.
Example of a protect function that defers recover and logs the panic:
func protect(g func()) {
defer func() {
log.Println("done")
if err := recover(); err != nil {
log.Printf("run time panic: %v", err)
}
}()
log.Println("start")
g()
}The log package writes to standard error; fatal functions call os.Exit(1), while panic functions call panic after logging.
// panic_recover.go
package main
import "fmt"
func badCall() {
panic("bad end")
}
func test() {
defer func() {
if e := recover(); e != nil {
fmt.Printf("Panicing %s
", e)
}
}()
badCall()
fmt.Printf("After bad call
") // not reached
}
func main() {
fmt.Printf("Calling test
")
test()
fmt.Printf("Test completed
")
}Running this program prints the recovered panic message and then continues.
Custom Package Error Handling and Panicking
Best practice for package authors: recover from internal panics within the package and return errors to callers; do not let panics escape the package boundary.
Example parse package with a custom ParseError type that panics on invalid input and recovers in Parse:
type ParseError struct {
Index int
Word string
Err error
}
func (e *ParseError) String() string {
return fmt.Sprintf("pkg parse: error parsing %q as int", e.Word)
}
func Parse(input string) (numbers []int, err error) {
defer func() {
if r := recover(); r != nil {
if e, ok := r.(error); ok {
err = e
} else {
err = fmt.Errorf("pkg: %v", r)
}
}
}()
fields := strings.Fields(input)
numbers = fields2numbers(fields)
return
}
func fields2numbers(fields []string) (numbers []int) {
if len(fields) == 0 {
panic("no words to parse")
}
for idx, field := range fields {
num, err := strconv.Atoi(field)
if err != nil {
panic(&ParseError{idx, field, err})
}
numbers = append(numbers, num)
}
return
}Using the package:
var examples = []string{
"1 2 3 4 5",
"100 50 25 12.5 6.25",
"2 + 2 = 4",
"1st class",
"",
}
for _, ex := range examples {
fmt.Printf("Parsing %q:
", ex)
nums, err := parse.Parse(ex)
if err != nil {
fmt.Println(err)
continue
}
fmt.Println(nums)
}Output shows successful parses and detailed error messages for invalid inputs.
Closure‑Based Error Handling Pattern
When all functions share the same signature, defer/panic/recover can be wrapped in a higher‑order function to eliminate repetitive error checks.
Helper check function panics on a non‑nil error:
func check(err error) {
if err != nil {
panic(err)
}
} errorHandlerreturns a wrapped function that recovers and logs any panic:
func errorHandler(fn fType1) fType1 {
return func(a type1, b type2) {
defer func() {
if e, ok := recover().(error); ok {
log.Printf("run time panic: %v", e)
}
}()
fn(a, b)
}
}Inside functions, replace repetitive if err != nil { … } blocks with check(err) calls.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Raymond Ops
Linux ops automation, cloud-native, Kubernetes, SRE, DevOps, Python, Golang and related tech discussions.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
