Background:
Go differs from many languages in its approach to error handling, where errors are values, not exceptions. This design was chosen for transparency and simplicity: whenever something might go wrong, a function explicitly returns the error as a second return value.
Problem:
Many beginners try to apply familiar try-catch patterns to Go, getting lost in the numerous error checks or failing to use error wrapping to convey context. This leads to loss of informativeness and difficult debugging.
Solution:
In Go, a function that can return an error is declared like this:
func doSomething() (ResultType, error) { // ... if somethingWrong { return nil, errors.New("something went wrong") } return result, nil }
Error checking is the responsibility of the caller:
res, err := doSomething() if err != nil { log.Fatalf("process failed: %w", err) }
Go 1.13 and above allow for "wrapping" errors using fmt.Errorf("%w", err) for building error chains. This improves diagnostics and promotes best practice for contextual error description.
Key Features:
Why create your own error types when you can just return errors.New("...")?
The correct answer: custom error types allow not only to convey the error message but also to retain additional information for further processing (e.g., return codes, context) and to implement finer handling through type assertion.
Example:
type NotFoundError struct { Resource string } func (e NotFoundError) Error() string { return fmt.Sprintf("%s not found", e.Resource) } // Check if _, ok := err.(NotFoundError); ok { // handle not found error }
Is panic a good replacement for error for critical errors?
No, panic in Go is used only in truly hopeless situations — for example, when there are errors in the program itself (program invariants), but not for ordinary failures (e.g., failed to open a file). Using panic to signal routine errors is an evil and leads to unreadable, unmanageable code.
What happens if error handling (err) is skipped in nested functions?
The error "gets lost," the code continues executing, which can lead to the spread of erroneous states. It is always important to properly handle each error return.
_ = ...Each function simply returns errors.New(...), errors are not wrapped, error types vary, but the handling is always the same — logged and thrown. As a result, log files are filled with uninformative messages, making it impossible to trace them back to the root cause.
Pros:
Cons:
Error wrapping is used via fmt.Errorf("%w", err), custom errors with useful fields, and checks through errors.Is()/errors.As(). As a result, logging with details can be done, business errors can be separated from environmental failures, and reliable retry logic can be implemented.
Pros:
Cons: