SoFunction
Updated on 2025-03-05

Detailed explanation of Error's design and practice in Golang

If you are not familiar with Go's Error design and are not used to it, why do many interfaces need to return a value of the error interface type? When should an error be processed, when should an error be thrown, and when should an error be ignored? Why do Go designers design errors like this? I believe that students who have just come into contact with Golang will have similar doubts like me. After reading the chapters/content related to TGPL and Go Blog, I try to answer these questions.

In sections 1 and 2 I will try to answer what error is, how it is designed, and why it is designed in this way.

In subsection 3 I will answer how to handle errors when Coding.

What is Error

In the Go built-in package, Error is designed as an interface.

// The error built-in interface type is the conventional interface for
// representing an error condition, with the nil value representing no error.
type error interface {
   Error() string
}

Go's design philosophy is: failure is just a common behavior.

Therefore, for functions that fail for granted, an additional result - an error, usually the last return value. If there is only one possible reason for failure, then you only need to return a bool value.

The above practices are very common in Go's source code or interface design. Let me give two examples:

Taking the common Reader interface as an example, the Read method reads up to len(p) bytes into p, returning the number of bytes read n and the error err that may occur during the reading process.

type Reader interface {
   Read(p []byte) (n int, err error)
}

A common situation when we use map is to determine whether a key is in the map. However, map will also return the default value when the key does not exist. At this time, you can use the form with the return value of bool:

if val, ok := m["key"]; ok {
    // do something
} else {
    // do other things
}

Error's design

Go's error handling design is different from exceptions in other languages. The error in Go is a normal value object, while other languages ​​such as Exception in Java will cause the program control flow to terminate and other behaviors, and Exception is different from ordinary values. Although Go has a similar exception mechanism - panic, it is only used to report completely unpredictable errors (possibly bugs) and should not be a program error that a robust program should return (this is different from languages ​​such as Java).

Kernighan explained in The Go Programming Language: "The reason for this design is that exceptions tend to entangle the description of an error with the control flow required to handle it, often leading to an undesirable outcome: routine errors are reported to the end user in the form of an incomprehensible stack trace, full of information about the structure of the program but lacking intelligent context about what went wrong".

That is: because exceptions entangle the description of the error and the control flow for handling the error, it usually causes program errors to be reported to the end user in an incomprehensible stack trace, which is full of information about the program structure, but lacks easy-to-understand context information about where the error is made.

Instead, Go programs use ordinary program control flow mechanisms such as if and return to respond to errors. Although this design requires Gophers to pay more attention to error handling logic, this is exactly what it wants to do. That is, "a good program should take into account all possible errors and process them."

Go designs error as an interface, and only needs to implement the Error() string method to return meaningful and concise error description information. This also allows us to customize the error in any way.

Tips: It is recommended that you only need to return clear error messages at the bottom, each layer wraps up some important and concise context information, and finally handle the error at the top of the program or at a level that has to be processed.

It is precisely this way that in Go, this layer-by-layer error is called an error chain. As a result, some new designs emerged after Go 1.13 to support the handling of this error chain. The simplest error chain is the text information wrapped in layers (or program call stack information) as described below.

genesis: crashed: no parachute: G-switch failed: bad relay orientation

Error Processing Policy

The most common practice is to pass errors. The errors generated by the called program are passed to the caller, and the upper layer decides how to deal with them, and some context information known to the program can be attached if necessary.

obj, err := doSomething()
if err != nil {
    return err
}
// do otherthings

The second type is that for those errors that represent short-term and unpredictable, you can try the operation again, and of course, a certain number of retry limits are required.

for i := 0; i < times; i++ {
    res, err := run()
    if err == nil {
        return res
    }
    // do something like log or metrics
}

The third type: If the execution cannot be continued, the caller prints the error message and ends the program gracefully.

if err := initT(); err != nil {
    panic("something wrong") // though this way is not elegant
}

The fourth type is that in some cases the error is not fatal, and it can just record the error and continue to execute it. This situation can at most cause the program to lack some functions, but it is better than doing nothing.

obj, err := doSomething()
if err != nil {
    (ctx, "something wrong but it doesnot cause serious consequences")
}
// and continue to do something

The last one, the caller is sure that an error that cannot occur or that will not be any problem even if it happens, and can ignore it.

// could not encode failed
bytes, _ := (obj)

Finally, Go's error handling is quite special. Generally, after checking for errors, it first deals with the failure situation and then handles the successful situation - "Happy Path". It can ensure that all errors are processed and then happily handle normal situations (remind Programmer not to forget to handle exceptions), and can reduce the indentation level (also known as "Guard" mode in other languages).

obj, err := getObj()
if err != nil {
    // do some err handling policy
    return ("could not get obj, err = %v", err)
}
// happy path

This is the end of this article about the detailed explanation of Error in Golang. For more related golang error content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!