Go > Error Handling > Built-in Error Interface > Error type
Creating Custom Error Types in Go
This example demonstrates how to define and use custom error types in Go, providing more context and information about errors that occur in your application. We'll explore the benefits of using custom error types for improved error handling and debugging.
Concepts Behind the Snippet
Go's `error` interface is a fundamental part of its error handling mechanism. It's defined as `type error interface { Error() string }`. While you can return simple string errors using `errors.New()`, creating custom error types allows you to attach additional data and methods to the error, making it easier to handle and debug specific error conditions. A custom error type is simply a struct that implements the `error` interface by providing an `Error() string` method.
Defining a Custom Error Type
In this example, `InsufficientFundsError` is a custom error type. It's a struct that holds information about the account, requested amount, current balance, and the timestamp of the failed transaction. The `Error()` method formats this information into a user-friendly string.
package main
import (
"fmt"
"time"
)
type InsufficientFundsError struct {
accountID string
requestedAmount float64
currentBalance float64
timestamp time.Time
}
func (e InsufficientFundsError) Error() string {
return fmt.Sprintf("Account %s: Insufficient funds. Requested: %.2f, Available: %.2f, Time: %s", e.accountID, e.requestedAmount, e.currentBalance, e.timestamp.Format(time.RFC3339))
}
func withdraw(accountID string, amount float64, balance float64) error {
if amount > balance {
return InsufficientFundsError{
accountID: accountID,
requestedAmount: amount,
currentBalance: balance,
timestamp: time.Now(),
}
}
return nil
}
func main() {
err := withdraw("12345", 100, 50)
if err != nil {
// Type assertion to access the specific error type
if insufficientFundsErr, ok := err.(InsufficientFundsError); ok {
fmt.Println("Withdrawal failed:", insufficientFundsErr.Error())
fmt.Println("Account ID:", insufficientFundsErr.accountID)
fmt.Println("Requested Amount:", insufficientFundsErr.requestedAmount)
fmt.Println("Current Balance:", insufficientFundsErr.currentBalance)
fmt.Println("Timestamp:", insufficientFundsErr.timestamp)
} else {
fmt.Println("An unexpected error occurred:", err)
}
} else {
fmt.Println("Withdrawal successful.")
}
}
Real-Life Use Case
Custom error types are beneficial in scenarios where you need to provide specific details about the error. For instance: * **Database Errors:** Include the query, affected rows, or database connection details. * **API Errors:** Store the HTTP status code, error message from the API, and the request/response data. * **Validation Errors:** Store the field that failed validation, the validation rule that was violated, and the invalid value.
Best Practices
* **Clarity:** Make sure the `Error()` method returns a clear and informative message. * **Context:** Include relevant context in the error type to aid in debugging. * **Type Assertion:** Use type assertion (`err.(MyErrorType)`) carefully. Only assert the type if you're confident about the error type or use the `ok` idiom to check before accessing fields specific to that error. * **Error Wrapping:** Consider wrapping errors using `fmt.Errorf("original error: %w", err)` to preserve the original error's context while adding more information.
Interview Tip
When discussing error handling in Go during an interview, be prepared to explain the benefits of custom error types over simple string errors. Emphasize the increased context, better debugging capabilities, and the ability to handle specific error conditions gracefully using type assertions.
When to Use Them
Use custom error types when you need more than just a simple error message. They are particularly useful when you need to inspect the error's contents programmatically to make decisions about how to handle the error.
Memory Footprint
The memory footprint of a custom error type depends on the fields you include in the struct. Generally, it's comparable to the memory used by a struct with similar fields. Consider the size of the data you're storing in the error type to avoid excessive memory usage.
Alternatives
Alternatives to custom error types include: * **String errors:** Useful for simple errors without the need for additional context. * **Error wrapping (using `%w` in `fmt.Errorf`):** Allows you to add context to existing errors without creating new types. Useful for propagating errors up the call stack. * **Sentinel errors (using `errors.Is` and `errors.As`):** Define specific error values that can be compared directly. Useful for known, specific errors.
Pros
* **Increased Context:** Provides more detailed information about the error. * **Improved Debugging:** Makes debugging easier by providing specific error information. * **Type-Specific Handling:** Allows you to handle different error types in different ways.
Cons
* **Increased Complexity:** Requires defining a new type for each specific error condition. * **Type Assertions:** Requires type assertions to access the specific error information, which can make the code less readable if overused.
FAQ
-
Why use custom error types instead of just returning a string error?
Custom error types allow you to attach more information to the error, such as relevant data that caused the error, timestamps, and other context. This enables better debugging and more precise error handling by allowing you to examine the error's contents programmatically. -
How do I check if an error is a specific custom error type?
You can use type assertion with the `ok` idiom: `if err, ok := err.(MyErrorType); ok { /* handle the error */ }`. This checks if the error is of the `MyErrorType` and, if so, assigns the error value to the `err` variable. -
Is it always necessary to create a custom error type for every error condition?
No. Simple string errors are sufficient for basic error conditions where you don't need to provide additional context. Custom error types are most beneficial when you need to provide detailed error information or handle specific error conditions differently based on the error type.