What Happens When Things Go Wrong?
We've learned to write functions, create structs, and use pointers. Our code works perfectly... in a perfect world. But in the real world, things go wrong: a file might not exist, a network connection might fail, or a user might provide invalid input.
Many languages handle these situations with "exceptions" using try-catch
blocks. Go takes a fundamentally different and more explicit approach.
In Go, errors are values. An error is not a special event that stops your program; it's just another value that a function can return, like a string
or an int
. This philosophy forces you to consciously handle potential failures, leading to more robust and predictable code.
1. The error
Type: Go's Built-in Contract
At its core, Go's error
is a built-in interface. It's a very simple contract: any type that wants to be considered an error
must have a single method called Error()
that returns a string.
type error interface {
Error() string
}
You don't usually need to create your own error types from scratch, but understanding this helps you see why the system is so flexible.
2. The Idiomatic Go Pattern: if err != nil
Because errors are just values, the standard way to handle them is to check if an error was returned from a function. This leads to the most common pattern you will see in any Go codebase: if err != nil
.
A function that can fail will typically return two values: (the result, an error)
.
Analogy: A service technician's report. When the job is done, you get two things: the fixed appliance (the result) and a report slip (the error).
- If the job was successful, the report slip is blank (
nil
). - If something went wrong, the report slip has a description of the problem (
not nil
).
The Pattern in Action
// A conceptual example
result, err := someFunctionThatCanFail()
if err != nil {
// An error occurred! Handle it here.
// For now, we can just print it and stop.
fmt.Println("An error happened:", err)
return
}
// If the code reaches this point, it means 'err' was nil.
// It is now safe to use the 'result'.
fmt.Println("Success! The result is:", result)
3. Creating and Returning Errors
Now that we know how to check for errors, how do we create our own? Go's built-in errors
package makes this very simple.
Let's build a practical example: a safe division function that returns an error if you try to divide by zero.
Code Example
package main
import (
"errors"
"fmt"
)
// This function returns two values: a float64 AND an error
func divide(a float64, b float64) (float64, error) {
// If the divisor is zero, it's a predictable failure
if b == 0 {
// We return a zero value for the number and a new error message
return 0, errors.New("cannot divide by zero")
}
// If everything is okay, we return the result and 'nil' for the error
// 'nil' is Go's way of saying "nothing" or "no error"
return a / b, nil
}
func main() {
// --- Successful case ---
result, err := divide(10.0, 2.0)
if err != nil {
// This block is skipped because 'err' is nil
fmt.Println("Error:", err)
} else {
fmt.Println("Result 1:", result)
}
// --- Failure case ---
result2, err2 := divide(10.0, 0)
if err2 != nil {
// This block runs because an error was returned
fmt.Println("Error:", err2)
} else {
fmt.Println("Result 2:", result2)
}
}
This pattern of returning (result, nil)
on success and (zero-value, error)
on failure is fundamental to writing clean and robust Go code.
4. error
vs. panic
: A Crucial Distinction
Beginners often wonder when to return an error
and when to use panic
. The difference is a matter of expectation.
Analogy: Think of your program as a car journey.
-
error
: This is like a "Check Engine" light. It's an expected problem. You can see the warning, decide to pull over, and handle it. The journey can potentially continue. -
panic
: This is like the engine suddenly exploding. This is a catastrophic, unexpected failure that indicates something is deeply wrong with the car (a bug in your code). The journey stops immediately.
Use this as your guide:
- Use
error
for expected failures that are a normal part of your program's operation (e.g., invalid user input, file not found, network timeout). - Use
panic
very rarely, and only for truly exceptional situations that indicate a programmer error or a state that should be impossible to reach.
Conclusion
Go's approach to error handling is one of its most defining features. By treating errors as regular values, it encourages you to build resilient and predictable applications.
You've now learned:
- The philosophy that "errors are values".
- The standard
if err != nil
pattern for checking errors. - How to create and return your own errors.
- The critical difference between a manageable
error
and a fatalpanic
.
In the final part of our foundational series, we'll learn how to structure larger projects by organizing our code into Packages. See you there!
Top comments (0)