09. Error Handling
Errors
Go treats an error as a value. Such errors do not have the potential to break the programs, but they are designed to denote the anomaly in the business logic
In Go it’s idiomatic to communicate errors via an explicit, separate return value. This contrasts with the exceptions used in languages like Java and Ruby and the overloaded single result / error value sometimes used in C.
Go’s approach makes it easy to see which functions return errors and to handle them using the same language constructs employed for any other, non-error tasks.
Returning an error
By convention, errors are the last return value and have type error, a built-in interface:
errors.New
constructs a basic error value with the given error message.
Receiving an error
Functions often return an error value, and calling code should handle errors by testing whether the error equals nil
. A nil
error denotes success; a non-nil
error denotes failure.
Wrapping an error
When an error is passed back through your code, you often want to add additional context to it. This context can be the name of the function that received the error or the operation it was trying to perform. When you preserve an error while adding additional information, it is called wrapping the error. When you have a series of wrapped errors, it is called an error chain.
The fmt.Errorf
function has a special verb, %w
. Use this to create an error whose formatted string includes the formatted string of another error and which contains the original error as well. If you want to create a new error that contains the message from another error, but don’t want to wrap it, use fmt.Errorf
to create an error, but use the %v
verb instead of %w
:
The standard library also provides a function for unwrapping errors, the Unwrap
function in the errors package. You pass it an error and it returns the wrapped error, if there is one.
You don’t usually call errors.Unwrap
directly. Instead, you use errors.Is
and errors.As
to find a specific wrapped error.
If you want to wrap an error with your custom error type, your error type needs to implement the method Unwrap
.
To check if the returned error or any errors that it wraps match a specific sentinel error instance, use errors.Is
. It takes in two parameters, the error that is being checked and the instance you are comparing against. The errors.Is
function returns true if there is an error in the error chain that matches the provided sentinel error. By default, errors.Is
uses ==
to compare each wrapped error with the specified error. If this does not work for an error type that you define (for example, if your error is a noncomparable type), implement the Is method on your error:
The errors.As
function allows you to check if a returned error (or any error it wraps) matches a specific type. It takes in two parameters. The first is the error being examined and the second is a pointer to a variable of the type that you are looking for.
Error type
Go programs express error state with error values. The error type is a built-in interface:
Example:
errors.New
errors.New
errors.New
function takes a string that it converts to an errors.errorString
and returns as an error value:
Sentinel errors
Sentinel errors is there are specific error created as values to indicate specific cases. They are one of the few variables that are declared at the package level. By convention, their names start with Err
. They should be treated as read-only; there’s no way for the Go compiler to enforce this, but it is a programming error to change their value.
Sentinel errors are usually used to indicate that you cannot start or continue processing. Be sure you need a sentinel error before you define one. Once you define one, it is part of your public API and you have committed to it being available in all future backward-compatible releases.
Handling panic and recover
A panic is similar to an exception which can occur at runtime. A panic can be thrown by the runtime or can be deliberately thrown by the programmer to handle the worst case scenario. For example, accessing the out of bounds element from a slice (or an array) will throw a panic.
Since the only functions which will get executed when the panic occurs are deferred function, we need to use recover
function inside defer
function.
The recover
function returns the value passed to panic
function and has no side effects. That means if our goroutine is not panicking, recover
function will return nil
. So checking the return value of recover()
to nil
is a good way to know if your program is packing, unless some idiot passes nil
to the panic
function which is a very rare case.
func panic(interface{})
func recover() interface{}
When the Go program panics, Go doesn’t just start unwinding the deferred function stack all at once, that’s not how deferred functions work. If a panic occurs in a function, its deferred function will be called and if program recovers inside those deferred functions, normal execution flow begins after that function exits.
No statements after the function call statement, which panicked, will be executed.
Когда случается
panic
, текущий фрейм функции уничтожается. Можно обработатьpainc
вdefer
-функции, но фрейм это не вернет. Выполнение продолжится в вызвавшей функции, но если функция в которой слилась panic должна была возвращать значение, то по-видимому это значение будет повреждено -- будет возвращено пустое значение для своего типа. Проблему можно поправить путем named return value подменив значение на нормальное в defer.Так же named return values позволяют нормально вернуть значение в случае panic.
Итого, или используй named return values или обрабатывай panic выше по стеку. Будь аккуратен с поврежденным return/
Использование:
Функция
panic(arg)
приостанавливает выполнение текущей функцииF
.Все
defer
функции отF
выполняются, а так же все связанные с ними прямые и косвенные вызовы других функций.После выполнения
defer
функций начинается раскрутка стека.Раскрутка стека продолжается пока
panic
не будет перехвачен вызовомrecover()
Возвращаемым значением из
recover()
будет значениеarg
переданное вpanic()
.
In order to recover a panicking program successfully, a deferred function with recover
call must be executed right after the panic. Because the recover
function does not know if the program panicked because from where it was called, everything was OK.
When a panic is detected in a goroutine, we can create a new goroutine for it. An example:
At any give time, a function call may associate with at most one unrecovered panic. If a call is associating with an unrecovered panic, then
the call will associate with no panics when the unrecovered panic is recovered.
when a new panic occurs in the function call, the new one will replace the old one to be the associating unrecovered panic of the function call.
For example, in the following program, the recovered panic is panic 3, which is the last panic associating with the main
function call.
runtime.Goexit
When the runtime.Goexit
function is called in a function call, we say a Goexit signal starts associating with the function call after the the runtime.Goexit
call fully exits. A panic and a Goexit signal are independent of each other. Associating either a panic or a Goexit signal with a function call will make the function call enter its exiting phase immediately.
As Goexit signals can't be cancelled, arguing whether a function call may associate with at most one or more than one Goexit signal is unnecessary.
Capture stack trace
Last updated
Was this helpful?