Go > Error Handling > Built-in Error Interface > Custom error types
Custom Error Types in Go
This example demonstrates how to define and use custom error types in Go, enhancing error handling and providing more context to the caller.
Defining a Custom Error Type
This code defines a custom error type called InsufficientFundsError
. It's a struct containing fields relevant to the error condition: the account ID, the requested amount, and the current balance. The key to making this a proper error type is implementing the Error()
method. This method returns a string representation of the error, conforming to the error
interface.
package main
import (
"fmt"
)
type InsufficientFundsError struct {
AccountID string
RequestedAmount float64
CurrentBalance float64
}
func (e *InsufficientFundsError) Error() string {
return fmt.Sprintf("account %s has insufficient funds. Requested: %.2f, Available: %.2f", e.AccountID, e.RequestedAmount, e.CurrentBalance)
}
Using the Custom Error Type
This code defines a withdraw
function that can return either the new balance or an InsufficientFundsError
. The function checks if the requested amount exceeds the current balance. If it does, it creates a new InsufficientFundsError
instance with the relevant details and returns it. In the main
function, the code calls withdraw
and checks for an error. If an error occurs, it uses errors.As
to check if the error is of type InsufficientFundsError
. If so, the specific error details (account ID, requested amount, and current balance) are printed. This is much more informative than just a generic error message.
package main
import (
"errors"
"fmt"
)
type InsufficientFundsError struct {
AccountID string
RequestedAmount float64
CurrentBalance float64
}
func (e *InsufficientFundsError) Error() string {
return fmt.Sprintf("account %s has insufficient funds. Requested: %.2f, Available: %.2f", e.AccountID, e.RequestedAmount, e.CurrentBalance)
}
func withdraw(accountID string, amount float64, balance float64) (float64, error) {
if amount > balance {
return balance, &InsufficientFundsError{AccountID: accountID, RequestedAmount: amount, CurrentBalance: balance}
}
return balance - amount, nil
}
func main() {
newBalance, err := withdraw("12345", 100.00, 50.00)
if err != nil {
var insufficientFundsErr *InsufficientFundsError
if errors.As(err, &insufficientFundsErr) {
fmt.Println("Withdrawal failed:", insufficientFundsErr)
fmt.Printf("Account ID: %s\n", insufficientFundsErr.AccountID)
fmt.Printf("Requested Amount: %.2f\n", insufficientFundsErr.RequestedAmount)
fmt.Printf("Current Balance: %.2f\n", insufficientFundsErr.CurrentBalance)
} else {
fmt.Println("Withdrawal failed with a generic error:", err)
}
return
}
fmt.Println("Withdrawal successful. New balance:", newBalance)
}
Concepts Behind the Snippet
The core concept is that Go's error
interface is satisfied by any type that implements the Error() string
method. By creating custom structs and implementing this method, you can create error types that carry specific data related to the error. This allows for more detailed error handling and reporting. The errors.As
function is used to determine if an error is of a specific type. This allows you to handle different types of errors differently.
Real-Life Use Case
Imagine a system that processes financial transactions. Different types of errors can occur: insufficient funds, invalid account number, transaction timeout, etc. Using custom error types, you can represent each of these errors as distinct types, each carrying relevant information. For example, a TransactionTimeoutError
might include the transaction ID and the timestamp of the timeout, allowing for more sophisticated debugging and retry logic.
Best Practices
errors.As
: Use errors.As
to check for specific error types. Avoid type assertions directly, as they can lead to panics.fmt.Errorf("%w", originalError)
to preserve the original error's context while adding more information.
Interview Tip
Be prepared to explain the benefits of custom error types over simple strings. Emphasize the ability to carry specific error data and the improved error handling capabilities that come with it. Also, understand the difference between errors.Is
and errors.As
. errors.Is
compares errors directly, while errors.As
checks if an error implements a specific interface or is of a specific type.
When to Use Them
Use custom error types when you need to provide more information about an error than a simple string can convey. They're particularly useful when the calling code needs to make decisions based on the specific type of error that occurred. Use simple errors when you only need to indicate that an error occurred without any specific context.
Memory Footprint
The memory footprint of a custom error type depends on the size of the fields it contains. A struct with a few string and numeric fields will typically have a small memory footprint. Avoid including large data structures in your error types unless absolutely necessary, as this can increase memory consumption and potentially impact performance.
Alternatives
errors.Is
).
Pros
Cons
FAQ
-
When should I use a custom error type vs. a simple error string?
Use custom error types when you need to provide more context about the error, such as specific details that the calling function can use to make decisions. Use simple error strings when you only need to indicate that an error occurred without any specific context. -
How do I check if an error is of a specific custom type?
Use theerrors.As
function to check if an error is of a specific custom type. This function safely checks if the error implements the target interface or is of the target type. -
What is the purpose of the
Error() string
method?
TheError() string
method is what makes a type anerror
in Go. Any type that implements this method can be returned as an error value. The method should return a human-readable string representation of the error.