Error Handling
Dealing with failures
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 dealing with failures.
Go has its own approach to dealing with failures, 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 (
"errors"
"fmt"
"os"
)
var ErrInvalidPath = errors.New("invalid path")
func readConfig(path string) (string, error) {
if path == "" {
return "", ErrInvalidPath
}
data, err := os.ReadFile(path)
if err != nil {
return "", fmt.Errorf("readConfig: %w", err)
}
return string(data), nil
}
func main() {
data, err := readConfig("config.txt")
if err != nil {
if errors.Is(err, ErrInvalidPath) {
fmt.Println("no path provided")
} else {
fmt.Println("error:", err)
}
os.Exit(1)
}
fmt.Println("config:", data)
}Comparing to C
Here's how you might have written similar code in C:
#include <stdio.h>
#include <errno.h>
#include <string.h>
/* Return code convention: 0 = success, non-zero = error */
int read_config(const char *path, char *buf, int len) {
FILE *f = fopen(path, "r");
if (!f) {
return errno; /* set by fopen */
}
if (!fgets(buf, len, f)) {
fclose(f);
return -1;
}
fclose(f);
return 0; /* success */
}
int main() {
char buf[256];
int err = read_config("config.txt", buf, sizeof(buf));
if (err != 0) {
fprintf(stderr, "error: %s\n", strerror(err));
return 1;
}
printf("config: %s\n", buf);
}You may be used to different syntax or behavior.
Go returns error as a value; C uses errno or return codes
You may be used to different syntax or behavior.
Go's errors.Is checks error identity; C compares integer codes
You may be used to different syntax or behavior.
Go's fmt.Errorf %w wraps errors with context; C has no wrapping convention
You may be used to different syntax or behavior.
Go forces you to handle errors at every call site; C's errno is global and easily ignored
Step-by-Step Breakdown
1. Error as Return Value
Go returns errors as the last return value — cleaner than C's errno global and impossible to accidentally ignore.
int err = read_config(path, buf, sizeof(buf));
if (err != 0) { ... }data, err := readConfig(path)
if err != nil { ... }2. Error Wrapping
Go's fmt.Errorf with %w adds context to errors while preserving the original, enabling errors.Is for type checking up the chain.
return "", fmt.Errorf("readConfig: %w", err)
// Caller can: errors.Is(err, os.ErrNotExist)3. Sentinel Errors
Go sentinel errors (package-level variables) replace C's error code constants, enabling identity comparison with errors.Is.
#define ERR_INVALID_PATH -2var ErrInvalidPath = errors.New("invalid path")Common Mistakes
When coming from C, developers often make these mistakes:
- Go returns error as a value; C uses errno or return codes
- Go's errors.Is checks error identity; C compares integer codes
- Go's fmt.Errorf %w wraps errors with context; C has no wrapping convention
Key Takeaways
- Go returns error values; C uses errno or int return codes
- errors.Is replaces integer error code comparisons
- fmt.Errorf %w adds context while wrapping original error
- Go errors are impossible to silently ignore at compile time