Golang or Go has a robust error-handling mechanism that's integral to the language's design. While Go provides built-in error types, there are situations where you might need more control and context in your error handling.
This is where creating custom errors comes into play. Custom errors can give you more informative error messages and can be used to categorize different types of errors in your application.
In this article, we'll explore how to create and use custom errors in Golang effectively.
In Go, the error type is a built-in interface that looks like this:
type error interface { Error() string }
Any type that implements the Error() method with a string return type satisfies this interface and can be considered an error. This is simple but powerful because it allows you to create custom error types by simply implementing this method.
Here's a quick example of basic error handling in Go:
package main import ( "errors" "fmt" ) func main() { err := doSomething() if err != nil { fmt.Println("Error:", err) } } func doSomething() error { return errors.New("something went wrong") }
This example uses the errors.New function to create a basic error. While this is useful for simple cases, it lacks the ability to provide more context or to distinguish between different types of errors.
Custom errors are essential when you need more descriptive error messages or when you want to handle different types of errors differently. For example, you might want to return a specific error type when a file is not found, and another type when a file is corrupted. Custom errors can also carry additional data, making debugging easier and providing more detailed information to the caller.
To create a custom error in Go, you define a new type that implements the Error() method. Let's walk through an example.
Here’s how you can create a simple custom error:
package main import ( "fmt" ) type MyError struct { Code int Message string } func (e *MyError) Error() string { return fmt.Sprintf("Code %d: %s", e.Code, e.Message) } func main() { err := doSomething() if err != nil { fmt.Println("Error:", err) } } func doSomething() error { return &MyError{ Code: 404, Message: "Resource not found", } }
In this example, MyError is a custom error type that includes a code and a message. The Error() method formats these into a string, making it easy to print or log the error.
Sometimes, it's useful to compare errors directly. This is where sentinel errors come in. A sentinel error is a predefined, exported variable that represents a specific error.
package main import ( "errors" "fmt" ) var ErrNotFound = errors.New("resource not found") func main() { err := doSomething() if errors.Is(err, ErrNotFound) { fmt.Println("Error:", err) } } func doSomething() error { return ErrNotFound }
While this approach is straightforward, combining sentinel values with custom error types can be even more powerful, allowing for both error comparison and rich error data.
Go 1.13 introduced the fmt.Errorf function with %w verb, which allows you to wrap errors, adding more context while preserving the original error.
package main import ( "errors" "fmt" ) var ErrNotFound = errors.New("resource not found") func main() { err := doSomething() if err != nil { fmt.Println("Error:", err) if errors.Is(err, ErrNotFound) { fmt.Println("The resource was not found.") } } } func doSomething() error { err := fetchResource() if err != nil { return fmt.Errorf("failed to do something: %w", err) } return nil } func fetchResource() error { return ErrNotFound }
This allows you to check for specific errors and also maintain a stack of errors that gives more context about what went wrong.
Go also provides a way to extract the wrapped error using the Unwrap() method. By implementing this method in your custom error types, you can allow further unwrapping of errors.
package main import ( "errors" "fmt" ) type MyError struct { Code int Message string Err error } func (e *MyError) Error() string { return fmt.Sprintf("Code %d: %s", e.Code, e.Message) } func (e *MyError) Unwrap() error { return e.Err } func main() { err := doSomething() if err != nil { fmt.Println("Error:", err) if errors.Is(err, ErrNotFound) { fmt.Println("The resource was not found.") } } } var ErrNotFound = errors.New("resource not found") func doSomething() error { err := fetchResource() if err != nil { return &MyError{ Code: 500, Message: "Something went wrong", Err: err, } } return nil } func fetchResource() error { return ErrNotFound }
Here, MyError has an Unwrap() method that returns the wrapped error. This allows for deeper inspection and handling of the underlying error.
Go also provides a way to extract the error with errors.As method. By implementing this method in your custom error types, you can check error type and also fetch the custom error values.
package main import ( "errors" "fmt" ) type MyError struct { Code int Message string Err error } func (e *MyError) Error() string { return fmt.Sprintf("Code %d: %s: %v", e.Code, e.Message, e.Err) } func main() { err := doSomething() var mErr *MyError // another way to initialize custom error // mErr := &MyError{} if errors.As(err, &mErr) { fmt.Println("Error:", mErr) } } // doSomething attempts to fetch a resource and returns an error if it fails. // If the error is ErrNotFound, it is wrapped in a MyError with a code of 500. func doSomething() error { err := fetchResource() if err != nil { return &MyError{ Code: 500, Message: "Something went wrong", Err: err, } } return nil } var ErrNotFound = errors.New("resource not found") func fetchResource() error { return ErrNotFound }
Use Custom Errors Sparingly: Custom errors are powerful but can add complexity. Use them when they provide significant benefits, like better error categorization or additional context.
Leverage Wrapping and Unwrapping: Wrapping errors with additional context and unwrapping them later is a best practice that enhances error debugging.
Document Your Error Types: Ensure that any custom errors are well-documented so that their purpose and usage are clear.
Prefer Error Values for Comparison: When you need to compare errors, consider using predefined error values (sentinel errors) for consistency and clarity.
Custom errors in Go provide a flexible and powerful way to manage errors in your applications. By creating your own error types, wrapping errors for additional context, and unwrapping them for deeper inspection, you can build robust and maintainable error-handling mechanisms. This not only helps in debugging but also improves the overall quality of your Go code.
Choose your strategy with the errors and use the consistent errors in the whole project.
Disclaimer: All resources provided are partly from the Internet. If there is any infringement of your copyright or other rights and interests, please explain the detailed reasons and provide proof of copyright or rights and interests and then send it to the email: [email protected] We will handle it for you as soon as possible.
Copyright© 2022 湘ICP备2022001581号-3