Go doesnât use exceptions. Instead, functions that can fail typically return an error as the last return value:
go
CopyEdit
func ReadFile(path string) ([]byte, error) { // implementation }
This style may look repetitive at first, but it offers:
â Predictable control flow
â Explicit error handling
â Simpler debugging
Go encourages you to handle errors as they happen, rather than rely on deferred panic recovery or centralized error traps.
go
CopyEdit
data, err := ReadFile("data.txt") if err != nil { log.Fatal(err) } fmt.Println(string(data))
This patternâcheck the error, handle it, and continueâis idiomatic and encouraged by the Go community.
go
CopyEdit
import "errors" func divide(a, b int) (int, error) { if b == 0 { return 0, errors.New("cannot divide by zero") } return a / b, nil }
go
CopyEdit
import "fmt" return 0, fmt.Errorf("failed to divide %d by %d: %w", a, b, err)
Use %w to wrap errorsâuseful for debugging or tracing root causes.
Bad:
go
CopyEdit
data, _ := ReadFile("file.txt")
Good:
go
CopyEdit
data, err := ReadFile("file.txt") if err != nil { return err }
Using _ ignores the error and is discouraged unless you have a very specific reason (e.g., test code or discard intentionally).
Create structured errors using custom types:
go
CopyEdit
type NotFoundError struct { Resource string } func (e *NotFoundError) Error() string { return fmt.Sprintf("%s not found", e.Resource) }
Now you can use type assertions to handle errors differently:
go
CopyEdit
err := getResource() if e, ok := err.(*NotFoundError); ok { fmt.Println("Handle not found:", e.Resource) }
Go 1.13 introduced native support for error chains via errors.Is and errors.As.
go
CopyEdit
return fmt.Errorf("read config: %w", err)
go
CopyEdit
if errors.Is(err, os.ErrNotExist) { fmt.Println("File does not exist") }
go
CopyEdit
var pathErr *os.PathError if errors.As(err, &pathErr) { fmt.Println("Error occurred at path:", pathErr.Path) }
â Ignoring errors (_)
â Overusing panic/recover (only for truly exceptional cases)
â Writing overly generic errors like return errors.New("failed")
â Returning wrapped errors without meaningful context
Handle errors as close to their source as possible
Use fmt.Errorf with %w for wrapping context
Use errors.Is() and errors.As() for comparison
Create custom error types for structured handling
Avoid panic() unless in initialization failures or truly unrecoverable errors
Log stack trace or full error chain in production
errors â Built-in package for wrapping and unwrapping
github.com/pkg/errors â Legacy favorite (still used for rich context)
github.com/hashicorp/go-multierror â Combine multiple errors
github.com/cockroachdb/errors â Advanced stack-tracing and telemetry
go
CopyEdit
func LoadConfig(file string) error { content, err := os.ReadFile(file) if err != nil { return fmt.Errorf("unable to read config %s: %w", file, err) } return nil }
In your main function:
go
CopyEdit
err := LoadConfig("config.yaml") if err != nil { log.Fatalf("startup failed: %v", err) }
Clean, contextual, and idiomatic.
Error handling in Go may seem verbose, but itâs designed for clarity and control. By following idiomatic practicesâlike checking errors explicitly, wrapping with context, and using custom typesâyouâll write more reliable and maintainable software.
In Go, handling errors well isn't just encouragedâit's part of the languageâs philosophy.
visit our website www.codriteit.com