Go handles errors using explicit error values instead of exceptions. The error
type is an interface with a single method:
type error interface {
Error() string
}
Any type implementing Error()
can be used as an error, allowing developers to create custom errors with meaningful context. This guide covers creating custom errors, implementing them, and advanced techniques for wrapping and unwrapping errors effectively.
Basic Error Handling in Go
Go's built-in error handling is based on returning an error type from functions. If an error is encountered, it is returned to the caller, who can decide what to do with it.
Go
package main
import (
"errors"
"fmt"
)
func main() {
// Call doSomething() and check if it returns an error
err := doSomething()
if err != nil {
fmt.Println("Error:", err)
}
}
func doSomething() error {
return errors.New("something went wrong")
}
In this example, errors.New
creates a simple error with a message. This is helpful for cases where you need a basic error, but it lacks context. For more complex scenarios, custom errors become essential.
Why Create Custom Errors?
Custom errors in Go are essential when:
- More Context is Required: Standard errors may not provide enough information. Custom errors allow you to embed additional data.
- Categorizing Errors: You might need to distinguish between different types of errors. For instance, a "file not found" error and a "permission denied" error could be categorized differently.
- Adding More Details: Custom errors can store metadata like error codes, timestamps, or the original cause of the error.
Custom errors give you the flexibility to add specific fields or methods that describe the error more precisely.
Creating Custom Errors in Go
To create a custom error in Go, you need to define a new type that implements the Error()
method. This method returns the error message as a string.
Example:
Go
package main
import (
"fmt"
)
// Custom error type that includes an error code and message
type MyError struct {
Code int
Message string
}
// Implementing the Error() method to satisfy the error interface
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, we’ve defined a custom error type MyError
that includes both a Code
and a Message
. The Error()
method formats these values into a string, making it easy to print or log the error.
Advanced Techniques for Handling Errors
1. Sentinel Errors and the errors.Is()
Method
A sentinel error is a predefined error value that you can compare to errors returned by functions. Go’s errors.Is()
method is useful when you need to check if an error is of a specific type or matches a known error.
Example:
Go
package main
import (
"errors"
"fmt"
)
// Predefined error variable for better error handling and comparison
var ErrNotFound = errors.New("resource not found")
func main() {
// Call doSomething() and check if it returns the specific error
err := doSomething()
// Using errors.Is() to compare the returned error with ErrNotFound
if errors.Is(err, ErrNotFound) {
fmt.Println("Error: Resource not found")
}
}
func doSomething() error {
// Returning a predefined error instead of creating a new one each time
return ErrNotFound
}
In this example, we define a sentinel error ErrNotFound
. We then use errors.Is()
to check if the error returned by doSomething()
matches this sentinel value.
2. Wrapping Errors for More Context
Go 1.13 introduced the %w
verb in fmt.Errorf()
for wrapping errors, enabling the preservation of the original error while adding more context.
Example:
Go
package main
import (
"errors"
"fmt"
)
// Predefined error for consistency and easier error comparison
var ErrNotFound = errors.New("resource not found")
func main() {
// Call doSomething() and check if it returns an error
err := doSomething()
if err != nil {
fmt.Println("Error:", err)
// Using errors.Is() to check if the error is ErrNotFound
if errors.Is(err, ErrNotFound) {
fmt.Println("The resource was not found.")
}
}
}
func doSomething() error {
// Calling fetchResource(), which may return an error
err := fetchResource()
if err != nil {
// Wrapping the error with additional context using %w for error chaining
return fmt.Errorf("failed to do something: %w", err)
}
return nil
}
func fetchResource() error {
// Simulating an error scenario by returning a predefined error
return ErrNotFound
}
In this example, fmt.Errorf()
is used to wrap the ErrNotFound
error with more context ("failed to do something"). The %w
verb ensures the original error is preserved, and you can still use errors.Is()
to check for the root cause.
3. Unwrapping Errors with Unwrap()
Go’s error system also allows you to unwrap errors. By implementing the Unwrap()
method in your custom error types, you allow deeper inspection of the underlying cause of an error.
Example:
Go
package main
import (
"errors"
"fmt"
)
// Custom error type that includes additional context: error code, message, and the original error
type MyError struct {
Code int
Message string
Err error
}
// The Error() method formats the error message, making it suitable for printing
func (e *MyError) Error() string {
return fmt.Sprintf("Code %d: %s", e.Code, e.Message)
}
func (e *MyError) Unwrap() error {
return e.Err
}
// Predefined error for a specific resource not being found
var ErrNotFound = errors.New("resource not found")
func main() {
err := doSomething()
if err != nil {
fmt.Println("Error:", err)
// Using errors.Is() to check if the error is ErrNotFound, even if wrapped
if errors.Is(err, ErrNotFound) {
fmt.Println("The resource was not found.")
}
}
}
func doSomething() error {
// Simulate an error returned by fetchResource() and wrap it in MyError
err := fetchResource()
if err != nil {
return &MyError{
Code: 500,
Message: "Something went wrong",
Err: err,
}
}
return nil
}
func fetchResource() error {
// Simulating a resource not found error
return ErrNotFound
}
In this example, MyError
implements the Unwrap()
method, which returns the original error (ErrNotFound
). This allows you to unwrap the error and inspect it for more context.
The errors.As()
function is useful when you want to extract the custom error type and access its specific fields.
Example:
Go
package main
import (
"errors"
"fmt"
)
// Custom error type containing additional details like code, message, and underlying error
type MyError struct {
Code int
Message string
Err error
}
// Error method returns the custom error message for MyError
func (e *MyError) Error() string {
return fmt.Sprintf("Code %d: %s", e.Code, e.Message)
}
func main() {
err := doSomething()
var mErr *MyError
if errors.As(err, &mErr) {
fmt.Println("Error:", mErr)
}
}
// Function that fetches resource and wraps an error if it fails
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
}
In this example, errors.As()
is used to check if the error is of type MyError
and extract its value.
Best Practices for Custom Errors
- Use Custom Errors When Necessary: Custom errors provide more detailed context, but they should be used only when they significantly improve error handling and debugging.
- Use Wrapping and Unwrapping: Wrapping errors with additional context allows for better error tracking. Use
Unwrap()
to gain deeper insights into the root cause. - Document Your Error Types: Always document the purpose and usage of custom error types so other developers can easily understand how they work.
- Prefer Error Values for Comparison: Use predefined, exported sentinel errors for easy and consistent error comparisons.
Conclusion
Custom errors in Go provide developers with a flexible and powerful tool to manage errors efficiently. By creating your own error types, wrapping errors with context, and unwrapping them when needed, you can build a robust error-handling mechanism that improves the overall quality of your Go applications. Whether you need more context for debugging, categorizing errors, or improving the clarity of your code, custom errors are an essential feature in Go.
Similar Reads
Go Tutorial Go or you say Golang is a procedural and statically typed programming language having the syntax similar to C programming language. It was developed in 2007 by Robert Griesemer, Rob Pike, and Ken Thompson at Google but launched in 2009 as an open-source programming language and mainly used in Google
2 min read
Overview
Fundamentals
Identifiers in Go LanguageIn programming languages, identifiers are used for identification purposes. In other words, identifiers are the user-defined names of the program components. In the Go language, an identifier can be a variable name, function name, constant, statement label, package name, or type. Example: package ma
3 min read
Go KeywordsKeywords or Reserved words are the words in a language that are used for some internal process or represent some predefined actions. These words are therefore not allowed to use as an identifier. Doing this will result in a compile-time error. Example: C // Go program to illustrate the // use of key
2 min read
Data Types in GoData types specify the type of data that a valid Go variable can hold. In Go language, the type is divided into four categories which are as follows: Basic type: Numbers, strings, and booleans come under this category.Aggregate type: Array and structs come under this category.Reference type: Pointer
7 min read
Go VariablesA typical program uses various values that may change during its execution. For Example, a program that performs some operations on the values entered by the user. The values entered by one user may differ from those entered by another user. Hence this makes it necessary to use variables as another
9 min read
Constants- Go LanguageAs the name CONSTANTS suggests, it means fixed. In programming languages also it is same i.e., once the value of constant is defined, it cannot be modified further. There can be any basic data type of constants like an integer constant, a floating constant, a character constant, or a string literal.
6 min read
Go OperatorsOperators are the foundation of any programming language. Thus the functionality of the Go language is incomplete without the use of operators. Operators allow us to perform different kinds of operations on operands. In the Go language, operators Can be categorized based on their different functiona
9 min read
Control Statements
Functions & Methods
Functions in Go LanguageIn Go, functions are blocks of code that perform specific tasks, which can be reused throughout the program to save memory, improve readability, and save time. Functions may or may not return a value to the caller.Example:Gopackage main import "fmt" // multiply() multiplies two integers and returns
3 min read
Variadic Functions in GoVariadic functions in Go allow you to pass a variable number of arguments to a function. This feature is useful when you donât know beforehand how many arguments you will pass. A variadic function accepts multiple arguments of the same type and can be called with any number of arguments, including n
3 min read
Anonymous function in Go LanguageAn anonymous function is a function that doesnât have a name. It is useful when you want to create an inline function. In Go, an anonymous function can also form a closure. An anonymous function is also known as a function literal.ExampleGopackage main import "fmt" func main() { // Anonymous functio
2 min read
main and init function in GolangThe Go language reserve two functions for special purpose and the functions are main() and init() function.main() functionIn Go language, the main package is a special package which is used with the programs that are executable and this package contains main() function. The main() function is a spec
2 min read
What is Blank Identifier(underscore) in Golang?_(underscore) in Golang is known as the Blank Identifier. Identifiers are the user-defined name of the program components used for the identification purpose. Golang has a special feature to define and use the unused variable using Blank Identifier. Unused variables are those variables that are defi
3 min read
Defer Keyword in GolangIn Go language, defer statements delay the execution of the function or method or an anonymous method until the nearby functions returns. In other words, defer function or method call arguments evaluate instantly, but they don't execute until the nearby functions returns. You can create a deferred m
3 min read
Methods in GolangGo methods are like functions but with a key difference: they have a receiver argument, which allows access to the receiver's properties. The receiver can be a struct or non-struct type, but both must be in the same package. Methods cannot be created for types defined in other packages, including bu
3 min read
Structure
Arrays
Slices
Slices in GolangSlices in Go are a flexible and efficient way to represent arrays, and they are often used in place of arrays because of their dynamic size and added features. A slice is a reference to a portion of an array. It's a data structure that describes a portion of an array by specifying the starting index
14 min read
Slice Composite Literal in GoThere are two terms i.e. Slice and Composite Literal. Slice is a composite data type similar to an array which is used to hold the elements of the same data type. The main difference between array and slice is that slice can vary in size dynamically but not an array. Composite literals are used to c
3 min read
How to sort a slice of ints in Golang?In Go, slices provide a flexible way to manage sequences of elements. To sort a slice of ints, the sort package offers a few straightforward functions. In this article we will learn How to Sort a Slice of Ints in Golang.ExampleGopackage main import ( "fmt" "sort" ) func main() { intSlice := []int{42
2 min read
How to trim a slice of bytes in Golang?In Go language slice is more powerful, flexible, convenient than an array, and is a lightweight data structure. The slice is a variable-length sequence which stores elements of a similar type, you are not allowed to store different type of elements in the same slice. In the Go slice of bytes, you ar
3 min read
How to split a slice of bytes in Golang?In Golang, you can split a slice of bytes into multiple parts using the bytes.Split function. This is useful when dealing with data like encoded strings, file contents, or byte streams that must be divided by a specific delimiter.Examplepackage mainimport ( "bytes" "fmt")func main() { // Initial byt
3 min read
Strings
Strings in GolangIn the Go language, strings are different from other languages like Java, C++, Python, etc. It is a sequence of variable-width characters where every character is represented by one or more bytes using UTF-8 Encoding. In other words, strings are the immutable chain of arbitrary bytes(including bytes
7 min read
How to Trim a String in Golang?In Go, strings are UTF-8 encoded sequences of variable-width characters, unlike some other languages like Java, python and C++. Go provides several functions within the strings package to trim characters from strings.In this article we will learn how to Trim a String in Golang.Examples := "@@Hello,
2 min read
How to Split a String in Golang?In Go language, strings differ from other languages like Java, C++, and Python. A string in Go is a sequence of variable-width characters, with each character represented by one or more bytes using UTF-8 encoding. In Go, you can split a string into a slice using several functions provided in the str
3 min read
Different ways to compare Strings in GolangIn Go, strings are immutable sequences of bytes encoded in UTF-8. You can compare them using comparison operators or the strings.Compare function. In this article,we will learn different ways to compare Strings in Golang.Examplepackage main import ( "fmt" "strings" ) func main() { s1 := "Hello" s2 :
2 min read
Pointers