Error handling in Go is crucial for building robust applications. By defining and using custom error types, developers can provide more context and specificity in error handling, thereby improving diagnostics and facilitating better error propagation. This section explores the creation of custom error types and the usage of error wrapping and unwrapping techniques using the fmt
and errors
packages.
Creating custom error types in Go allows for more descriptive and context-specific error messages. This approach helps in distinguishing between different kinds of errors, making it easier to handle them appropriately.
Example: Defining a Custom Error Type
gopackage main
import (
"fmt"
)
// Define a custom error type
type MyError struct {
Code int
Message string
}
func (e *MyError) Error() string {
return fmt.Sprintf("Error %d: %s", e.Code, e.Message)
}
func doSomething() error {
// Simulate an error
return &MyError{Code: 123, Message: "Something went wrong"}
}
func main() {
err := doSomething()
if err != nil {
fmt.Println(err)
}
}
In this example, MyError
is a custom error type that includes an error code and a message. The Error
method satisfies the error
interface, enabling instances of MyError
to be treated as standard errors.
Error wrapping is a technique used to add context to an error while preserving the original error. This is particularly useful for error propagation, allowing each layer of the application to add relevant context.
Example: Wrapping an Error
gopackage main
import (
"errors"
"fmt"
)
func doSomething() error {
return errors.New("original error")
}
func doSomethingElse() error {
err := doSomething()
if err != nil {
return fmt.Errorf("doSomethingElse failed: %w", err)
}
return nil
}
func main() {
err := doSomethingElse()
if err != nil {
fmt.Println(err)
}
}
In this example, the fmt.Errorf
function is used with the %w
verb to wrap the original error with additional context. This allows the doSomethingElse
function to add its own message while preserving the original error for further inspection.
Unwrapping Errors
To retrieve the original error, the errors.Unwrap
function or the errors.As
and errors.Is
functions can be used.
Example: Unwrapping an Error
gopackage main
import (
"errors"
"fmt"
)
func doSomething() error {
return errors.New("original error")
}
func doSomethingElse() error {
err := doSomething()
if err != nil {
return fmt.Errorf("doSomethingElse failed: %w", err)
}
return nil
}
func main() {
err := doSomethingElse()
if err != nil {
fmt.Println("Error occurred:", err)
if unwrappedErr := errors.Unwrap(err); unwrappedErr != nil {
fmt.Println("Unwrapped error:", unwrappedErr)
}
}
}
Here, errors.Unwrap
retrieves the original error that was wrapped, allowing for deeper inspection and more granular error handling.
errors.As
and errors.Is
The errors.As
function is used to check if an error can be assigned to a specific type, while errors.Is
is used to compare an error to a target error.
Example: Using errors.As
gopackage main
import (
"errors"
"fmt"
)
type MyError struct {
Code int
Message string
}
func (e *MyError) Error() string {
return fmt.Sprintf("Error %d: %s", e.Code, e.Message)
}
func doSomething() error {
return &MyError{Code: 123, Message: "something went wrong"}
}
func main() {
err := doSomething()
var myErr *MyError
if errors.As(err, &myErr) {
fmt.Printf("Custom error occurred: Code=%d, Message=%s\n", myErr.Code, myErr.Message)
} else {
fmt.Println("An error occurred:", err)
}
}
In this example, errors.As
checks if the error is of type *MyError
and, if so, extracts the custom error details.
Example: Using errors.Is
gopackage main
import (
"errors"
"fmt"
)
var ErrSpecific = errors.New("specific error")
func doSomething() error {
return fmt.Errorf("something went wrong: %w", ErrSpecific)
}
func main() {
err := doSomething()
if errors.Is(err, ErrSpecific) {
fmt.Println("A specific error occurred:", err)
} else {
fmt.Println("An error occurred:", err)
}
}
In this example, errors.Is
checks if the error matches ErrSpecific
, allowing for specific error handling.
Defining and using custom error types in Go enhances error handling by providing more context and specificity. Error wrapping and unwrapping techniques, facilitated by the fmt
and errors
packages, further improve error diagnostics and propagation. By leveraging these mechanisms, developers can build more resilient and maintainable applications.