Error Handling
Error Handling
Introduction
In this lesson, you'll learn about error handling in Go. Coming from C#, you already have a foundation for understanding this concept. We'll build on that knowledge while highlighting the key differences.
In C#, you're familiar with error handling.
Go has its own approach to error handling, which we'll explore step by step.
The Go Way
Let's see how Go handles this concept. Here's a typical example:
package main
import (
"fmt"
"os"
"strconv"
"strings"
)
func main() {
data, err := os.ReadFile("config.txt")
if err != nil {
fmt.Println("File not found:", err)
return
}
value, err := strconv.Atoi(strings.TrimSpace(string(data)))
if err != nil {
fmt.Println("Invalid format:", err)
return
}
fmt.Println("Value:", value)
}Comparing to C#
Here's how you might have written similar code in C#:
try {
var data = File.ReadAllText("config.txt");
var value = int.Parse(data.Trim());
Console.WriteLine($"Value: {value}");
}
catch (FileNotFoundException ex) {
Console.WriteLine($"File not found: {ex.Message}");
}
catch (FormatException ex) {
Console.WriteLine($"Invalid format: {ex.Message}");
}You may be used to different syntax or behavior.
Go has no exceptions — errors are returned as values (the last return value by convention)
You may be used to different syntax or behavior.
if err != nil is the idiomatic error check — everywhere
You may be used to different syntax or behavior.
No try/catch/finally — handle errors inline at each call site
You may be used to different syntax or behavior.
The error type is a built-in interface with a single Error() string method
You may be used to different syntax or behavior.
Go's defer keyword replaces finally for cleanup
Step-by-Step Breakdown
1. Error as Return Value
Go functions that can fail return (result, error) as their last return value. You must check the error every time — the compiler won't force you, but ignoring errors is considered a serious bug.
int Parse(string s) {
return int.Parse(s); // throws on failure
}import "strconv"
// Returns value AND error
n, err := strconv.Atoi("42")
if err != nil {
// handle the error
fmt.Println("parse error:", err)
return
}
// n is safe to use here2. Creating and Wrapping Errors
Use fmt.Errorf to create errors with context. The %w verb wraps an error so callers can inspect the original cause.
throw new InvalidOperationException(
$"Failed to load config: {innerEx.Message}", innerEx);import "fmt"
// Create a simple error
err := fmt.Errorf("invalid age: %d", age)
// Wrap an existing error (preserves cause)
wrapped := fmt.Errorf("loading config: %w", originalErr)
// Unwrap to inspect cause
import "errors"
var target *os.PathError
if errors.As(wrapped, &target) {
fmt.Println("path:", target.Path)
}3. defer for Cleanup
defer schedules a function call for when the surrounding function returns — equivalent to finally or using/IDisposable.
using var file = File.OpenRead("data.txt");
// file.Dispose() called automatically at end of using blockf, err := os.Open("data.txt")
if err != nil {
return err
}
defer f.Close() // runs when function returns, regardless of how
// Continue using f...4. panic and recover
Go has panic for truly unrecoverable errors and recover to catch them — similar to C# Exception but reserved for programming errors, not expected failures.
// C# — throw for expected and unexpected errors
throw new InvalidOperationException("unexpected state");// panic — for programmer errors, not expected conditions
func divide(a, b int) int {
if b == 0 {
panic("division by zero — this is a bug")
}
return a / b
}
// recover — catch panics in a deferred function
defer func() {
if r := recover(); r != nil {
fmt.Println("recovered from panic:", r)
}
}()Common Mistakes
When coming from C#, developers often make these mistakes:
- Go has no exceptions — errors are returned as values (the last return value by convention)
- if err != nil is the idiomatic error check — everywhere
- No try/catch/finally — handle errors inline at each call site
Key Takeaways
- No exceptions — errors are return values, checked with if err != nil
- Wrap errors with fmt.Errorf("context: %w", err) for traceable chains
- defer replaces finally and using/IDisposable for cleanup
- panic/recover exist for unrecoverable programmer errors only