Go > Error Handling > Built-in Error Interface > Returning and checking errors
Returning and Checking Errors in Go
This code snippet demonstrates how to return errors from functions and how to properly check for errors in Go using the built-in error
interface.
Introduction to Error Handling in Go
Go uses a straightforward approach to error handling. Functions that can potentially fail should return an error
as the last return value. The calling function then checks if the returned error is nil
(no error) or contains an error value. This promotes explicit error checking and makes error handling a fundamental part of the code.
The error
Interface
The error
interface is a built-in type in Go. It's a simple interface defined as:
type error interface { Error() string }
Any type that implements the Error() string
method can be used as an error. This allows for custom error types with specific error messages and contextual information.
Basic Error Handling Example
This example demonstrates a simple divide
function that returns an integer and an error. It checks for division by zero and negative inputs. If either condition is met, it returns an error using errors.New
. Otherwise, it returns the result of the division and nil
to indicate no error. The main
function calls divide
and checks the returned error. If an error is present, it prints the error message and exits the program; otherwise, it prints the result.
package main
import (
"errors"
"fmt"
)
// Function that returns an error if the input is negative.
func divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
if a < 0 || b < 0 {
return 0, errors.New("negative numbers are not allowed")
}
return a / b, nil
}
func main() {
result, err := divide(10, 2)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("Result:", result)
result, err = divide(5, 0)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("Result:", result)
result, err = divide(-1, 2)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("Result:", result)
}
Custom Error Types
This example demonstrates creating a custom error type TimeoutError
. It includes fields for the operation, timeout duration, and timestamp. The Error()
method is implemented to satisfy the error
interface, providing a formatted error message that includes the operation and timeout duration. The performOperation
function simulates a long-running operation and returns a TimeoutError
. In main
, the returned error is checked. We also see how to do a type assertion to check for a specific error type so that we can extract particular error details from the TimeoutError
struct.
package main
import (
"fmt"
"time"
)
// Custom error type.
type TimeoutError struct {
operation string
timeout time.Duration
timestamp time.Time
}
// Implement the error interface for TimeoutError.
func (e *TimeoutError) Error() string {
return fmt.Sprintf("Operation '%s' timed out after %v at %v", e.operation, e.timeout, e.timestamp.Format(time.RFC3339))
}
// Function that returns a custom TimeoutError.
func performOperation(op string) error {
time.Sleep(2 * time.Second)
return &TimeoutError{operation: op, timeout: 1 * time.Second, timestamp: time.Now()}
}
func main() {
err := performOperation("Database Query")
if err != nil {
if timeoutErr, ok := err.(*TimeoutError); ok {
fmt.Println("Timeout Error:", timeoutErr)
fmt.Println("Operation:", timeoutErr.operation)
fmt.Println("Timeout Duration:", timeoutErr.timeout)
fmt.Println("Timestamp:", timeoutErr.timestamp.Format(time.RFC3339))
} else {
fmt.Println("Generic Error:", err)
}
return
}
fmt.Println("Operation completed successfully.")
}
Concepts Behind the Snippet
The core concept behind this snippet is Go's explicit error handling philosophy. Instead of relying on exceptions, Go encourages functions to return errors as regular values. This forces developers to explicitly handle potential errors, leading to more robust and predictable code. The error
interface provides a standard way to represent errors, allowing for both simple string-based errors and more complex, custom error types.
Real-Life Use Case
Error handling is crucial in real-world applications. For example, when interacting with databases, network services, or file systems, errors can occur due to various reasons such as connection failures, invalid data, or permission issues. Proper error handling ensures that the application can gracefully handle these situations, log the errors, and potentially retry the operation or notify the user.
Best Practices
fmt.Errorf("%w", err)
. This preserves the original error while adding more information.
Interview Tip
Be prepared to discuss Go's error handling approach and the benefits of explicit error checking. Explain the error
interface and how to create custom error types. Also, understand the importance of providing informative error messages and wrapping errors for context.
When to Use Them
Use this pattern in every function that can potentially fail. This includes I/O operations (file access, network calls), database interactions, and any operation that depends on external resources or user input. Always consider the possible error scenarios and handle them accordingly.
Memory Footprint
The memory footprint of returning an error is generally small. The error
interface is a pointer under the hood, so returning an error (even a custom error struct) typically involves returning a pointer. If nil
is returned for the error, then there is no memory allocation for the error itself. Custom error types will consume memory proportional to the size of their fields. However, the overhead is usually minimal compared to the overall memory usage of the application.
Alternatives
While Go doesn't have traditional exceptions, there are alternative approaches for handling errors, such as: recover
function can be used to catch panics, but it's generally recommended to avoid panics for routine error handling.
Pros
error
interface and the error checking pattern are straightforward and easy to learn.
Cons
if err != nil
checks.
FAQ
-
What is the
error
interface in Go?
Theerror
interface is a built-in type in Go that represents an error. It's defined astype error interface { Error() string }
. Any type that implements theError() string
method can be used as an error. -
How do I create a custom error type in Go?
To create a custom error type, define a struct or other type and implement theError() string
method for it. This method should return a string representation of the error. -
When should I use custom error types?
Use custom error types when you need to provide additional information about the error, such as the operation that failed, the timeout duration, or other relevant context. This allows for more specific error handling. -
How do I check for a specific error type?
You can use a type assertion to check if an error is of a specific type. For example:if timeoutErr, ok := err.(*TimeoutError); ok { ... }
.