Go Language Error Handling: Difference between revisions

From NovaOrdis Knowledge Base
Jump to navigation Jump to search
 
(388 intermediate revisions by the same user not shown)
Line 1: Line 1:
=External=
* https://go.dev/blog/error-handling-and-go
* https://dave.cheney.net/2012/01/18/why-go-gets-exceptions-right
* https://dave.cheney.net/2014/11/04/error-handling-vs-exceptions-redux
* https://dave.cheney.net/2015/01/26/errors-and-exceptions-redux
* https://gabrieltanner.org/blog/golang-error-handling-definitive-guide
=Internal=
=Internal=
* [[Go_Language#Error_Handling|Go Language]]
* [[Go_Language#Error_Handling|Go Language]]
* [[Go Package errors]]
* [[Go_Error_Wrapping#Overview|Go Error Wrapping]]
* [[Go_Custom_Error_Types#Overview|Custom Error Types]]
* [[Go panic|<tt>panic</tt>]]
 
=TODO=
=TODO=


<font color=darkkhaki>
<font color=darkkhaki>
* Second pass needed.
* Investigate github.com/hashicorp/go-multierror and morph the learnings into this article.</font>
* Go had no exceptions, what is the idiomatic way to handle errors? Handling errors after each function call seems noisy.
</font>


=Overview=
=Overview=
This article provides an overview of error handling in Go. It starts with [[#Error_Basics|error basics]], where we explain that errors in Go are values, and functions return them as results. We continue by enumerating the ways error instances [[#Creating_Error_Instances|can be created]] and how the caller of a function that returns an error is supposed to [[#Handling_Errors|handle]] it. We also discuss [[#Concurrent_Programming_Error_Handling|how to handle errors in concurrent programming]].


A common error generation pattern in Go is that function return an error instance as the second return value. In case of correct operation, the second return value is <code>nil</code>. If there's an error, then the second parameter has a non-<code>nil</code> value.
=<span id='Idiom'></span><span id='Error_Handling_Idioms'></span>Error Basics=


Explicit error handling is some times cited as one of the strong points of Go.
A big part of programming is how you handle errors. Errors in Go are values of the <code>[[#error|error]]</code> type, not control structures. Java and Python's error handling mechanisms rely on exceptions, which are program flow control structures. Go is different, in that is has no exceptions, which implies that any error condition that you will encounter in a program is by definition not exceptional, but part of the normal flow of the program. This approach makes the statement that error handling is important, and as we develop our programs, we should give our error paths the same attention we give our algorithms. The closest thing to exceptions in Go is the [[Go panic|panic]] mechanism.


<span id='Error_Interface'></span>The returned error instance is implements the <code>error</code> [[Go_Interfaces#Overview|interface]]:
When an error condition occurs, which usually indicates that the system has entered in a state in which it cannot fulfill an operation that a user explicitly or implicitly requested, the function in question must return an error value in addition to the result via its [[Go_Functions#Multiple_Return_Values|support for multiple return values]]. The error is typically returned as the first and only argument, if the function does not return anything else, or the last argument otherwise:


<syntaxhighlight lang='go'>
<syntaxhighlight lang='go'>
type error interface {
func someFunc(...) error {
   Error() string
   ...
  err := ...
  return err
}
}
</syntaxhighlight>
</syntaxhighlight>
The <code>Error()</code> method prints the error message.
The <code>error</code> interface is a [[Go_Language#Pre-Declared_Types|pre-declared type identifier]].
Keep errors descriptive, yet compact. It should be easy to understand what exactly went wrong by reading the error message.
=<span id='Idiom'></span><span id='Error_Handling_Idioms'></span>Idiomatic Error Handling=
<syntaxhighlight lang='go'>
<syntaxhighlight lang='go'>
f, err := os.Open("...")
func someFunc(...) (result SomeType, [...], error) {
if err != nil {
   ...
   fmt.Println(err) // will call the Error() method
  err := ...
   return
   return ...,...,err
}
}
</syntaxhighlight>
</syntaxhighlight>
Note that the function might return a concrete type of an error instead of the <code>[[#The_error_Type_and_error_Values|error]]</code> interface, but this is usually a mistake. Returning the interface should be preferred.
If an error occurs, and the error value returned by the function is non-<code>nil</code>, all other values returned by the function must be ignored.


<syntaxhighlight lang='go'>
If no error occurs, the function must return <code>nil</code> as error value. In Go, <code>nil</code> means "no error".
f, err := os.Open("...")
if err != nil {
  return errors.New("something went wrong")
}
</syntaxhighlight>


<syntaxhighlight lang='go'>
When the error is created, it is the error implementation’s responsibility to summarize the context in the error message. The error string should start with a lowercase character, unless it begins with names that require capitalization. Error strings, unlike [[Go_Language#Comments|comments]], should not end with punctuation marks. Error messages should be descriptive, yet compact. It should be easy to understand what exactly went wrong by reading the error message. Descriptive elements in the message help the operators of the program to figure out what caused the error. For example, if a file cannot be opened because of lack of permission, the error message should be "open /some/path/myfile.txt: permission denied", not just "permission denied".
var somethingWentWrong = errors.New("something went wrong")


f, err := os.Open("...")
The caller of the function is supposed to [[#Handling_Errors|handle the error]]. <span id='ignoring'></span>An error returned by a function can be ignored if the caller assigns it to the blank identifier <code>[[Go_Language#The_Blank_Identifier_.28_.29|_]]</code>, but this approach is discouraged. Ignoring an error leads in most cases to bad outcomes. Handling an error means inspecting the error value and making a decision. Always handle the error. When handling the error, [[#Only_Handle_an_Error_Once|only handle it once]].
if err != nil {
  return somethingWentWrong
}
</syntaxhighlight>


Use panics in truly exceptional cases.
<span id='Do_Not_Program_Around_Error_Message'></span>The error message can be introspected by invoking <code>Error()</code> on the error instance. However, the result of this introspection should not be used in the program logic. That output is aimed at humans operating the program, not code. The contents of that string belong in a log file or displayed on the screen. You should not try to change the behavior of the program as result of processing the error message. If you need more context carried within the error instance, look into [[#Custom_Error_Types|custom error types]].


Do not discard errors by using the blank identifier <code>[[Go_Language#The_Blank_Identifier_.28_.29|_]]</code> assignment, aways handle each error.
A valid pattern to handle an error is augmenting the error with more context and returning it to the upper layer. When possible, simply [[#Handle_the_Error_as_Opaque_and_Return_It_Unmodified|return the error unchanged]]. If you must add more context, prefer [[#Wrap_the_Error_Instance|error wrapping]] over [[#Annotate_the_Error|annotation]].


When calling a function returning an error, always handle the error first.
<span id='Known_Edge_Cases'></span>Another error handling pattern is built around the fact that all errors belong to one of two categories: '''bugs''' and '''known edge cases''' (disk full, network failure, etc.). The pattern requires to represent the known edge cases as "well-formed" errors and declare them as part of our component API. All other errors are "raw errors" and represent bugs. When exported, errors become part of your package's public API and must be treated with as much care as you would any other part of your public API. This pattern is described here: [[Go_Raw_Errors_and_Well-Formed_Errors#Overview|Raw Errors and Well-Formed Errors]].


Error strings should start with a lowercase character, unless they begin with names that require capitalization.
Rob Pike's Go proverbs concerning errors: "[[Go_Proverbs#EAJV|Errors are just values]]" and "[[Go_Proverbs#DJCE|Don't just check errors, handle them gracefully]]". Also "error values in Go aren’t special, they are just values like any other, and so you have the entire language at your disposal".


Error strings, unlike [[Go_Language#Comments|comments]], should not end with punctuation marks.
Also see: {{Internal|Go_Style#Idiomatic_Error_Handling|Go Style}}


==Wrapping Errors==
=<span id='Creating_Error_Instances'></span>Error Instance Creation=
{{External|https://golang.org/pkg/fmt/#Errorf}}
==<tt>fmt.Errorf()</tt>==
<syntaxhighlight lang='go'>
err := fmt.Errorf("some %s error", "blue")
</syntaxhighlight>
The most common way to generate an error instance is <code>[[Go_Package_fmt#Errorf.28.29|fmt.Errorf()]]</code>. <code>fmt.Errorf()</code> is equivalent to <code>[[#errors.New.28.29|errors.New()]]</code>, in that it produces a pointer to a new <code>error</code> instance, but it also allows formatting the error text using the capabilities of the <code>[[Go_Package_fmt#Formatting|fmt]]</code> package. For this reason, <code>fmt.Errorf()</code> allows more flexibility and '''should be preferred''' over <code>errors.New()</code>. <code>fmt.Errorf()</code> invokes <code>errors.New()</code> internally. Each call to <code>fmt.Errorf()</code> returns a distinct error value even if the text is identical.
===Error Wrapping===
A new error instance can be created by wrapping an existing error with <code>fmt.Errorf()</code>. Error wrapping is an error handling idiom discussed in detail in [[#Wrap_the_Error_Instance|Wrap the Error Instance]] below.


<font color=darkkhaki>Define a wrapped error. Compare <code>fmt.Errorf()</code> to <code>errors.Unwrap()</code>.</font>
==<tt>errors.New()</tt>==
<syntaxhighlight lang='go'>
err := errors.New("some error")
</syntaxhighlight>
The <code>[[#The_errors_Package|errors]]</code> package <code>New()</code> function returns a pointer to a new error instance <code>*errors.errorString</code> that carries the given text. <code>errorString</code> is an unexported type of the <code>errors</code> package. Each call to <code>New()</code> returns a distinct error value even if the text is identical. The <code>New()</code> function does not have string formatting capabilities. The equivalent function <code>[[#fmt.Errorf.28.29|fmt.Errorf()]]</code> does and it should be preferred over <code>errors.New()</code>.


Existing errors can be wrapped in outer errors, which may add information relevant to the context that caught the error being processed. However, wrapping error is more than concatenating strings.
==Sentinel Errors==


The conventional way of wrapping errors in Go is to use the <code>%w</code> formatting clause with <code>fmt.Errorf()</code>:
Sentinels are a special category of errors that are specifically intended to be identifiable using an equality test, which implies that only one error instance is used throughout the runtime. The name comes from the practice in computer programming of using a specific value to signify that no further processing is possible. In this case, we use specific values to signify an error, and we check for the presence of that value with the equality <code>==</code> operator or with <code>[[Go_Error_Wrapping#errors.Is.28.29|errors.Is()]]</code> (preferred). A sentinel error instance must have a unique identity, to make the evaluation with the equality test possible. An example of a sentinel error is <code>io.EOF</code>:
<syntaxhighlight lang='go'>
<syntaxhighlight lang='go'>
if err != nil {
n, err := r.Read(buf)
  return fmt.Errorf("additional information: %w", err)
if err == io.EOF {
...
}
}
</syntaxhighlight>
</syntaxhighlight>
The wrapped error message, assuming that the original error is "initial information", with be:
<span id='NotYetImplemented_Declaration'></span>The typical way to declare and use a sentinel error:  
<font size=-1>
<font size=-2>
  additional information: initial information
  .
├── internal
│    ├── errs
│    │   ├── errs.go
</font>
</font>
===Checking for Wrapped Errors===
The package name is designed to avoid clashing with the built-in type <code>error</code>, with the <code>errors</code> package name and with the commonly used variable name <code>err</code>.
 
A wrapped error can be identified in an enclosing error with the <code>errors.Is(<outer_error>, <sought_for_error>)</code> function:
<syntaxhighlight lang='go'>
<syntaxhighlight lang='go'>
var BlueError = errors.New("some information")
package errs
var GreenError = errors.New("some information")


...
...


// wrap the error in an outer error
var NotYetImplemented = fmt.Errorf("not yet implemented")
outerError := fmt.Errorf("addtional info: %w", BlueError)
var NotFound = fmt.Errorf("not found")
</syntaxhighlight>
If it is not practical to create a new <code>errs</code> package and the errors must be declared in an existing package, it is OK to postfix the error instance name with <code>Error</code>:
<syntaxhighlight lang='go'>
package something
...
var NotYetImplementedError = fmt.Errorf("not yet implemented")
var NotFoundError = fmt.Errorf("not found")
</syntaxhighlight>


if errors.Is(outerError, BlueError) {
To check for occurrence, prefer <code>[[Go_Error_Wrapping#errors.Is.28.29|errors.Is()]]</code> over type assertions. <code>errors.Is()</code> is designed to work with both individual errors and [[Go_Error_Wrapping#Overview|wrapped errors]].
   fmt.Println("found blue error")
<syntaxhighlight lang='go'>
func someFunc() error {
   return errs.NotYetImplemented
}
}


if errors.Is(outerError, GreenError) {
...
   fmt.Println("found green error")
 
err := someFunc()
if errors.Is(err, errs.NotYetImplemented) {
   fmt.Printf("someFunc() is not yet implemented")
}
}
</syntaxhighlight>
</syntaxhighlight>
There are a few disadvantages to using sentinel errors.


<code>BlueError</code> is correctly identified, even though both <code>BlueError</code> and <code>GreenError</code> carry the same string. <font color=darkkhaki>How?</font>
The most serious is that the sentinel errors create a dependency between packages. For example, to check if an error is equal with <code>io.EOF</code>, you must import the <code>io</code> package. This particular example is not that bad, because <code>io</code> is quite common, but if this kind of coupling generalizes, it can become problematic. Using sentinel errors as part of the <code>internal</code> packages of a module and not exporting them as part of the public interface of the module is probably OK.


Another is that an attempt to [[#Annotate_the_Error|annotate]] the sentinel error to provide more context would cause returning a different error would break the equality check. This is a limitation that can be worked around by [[Go_Error_Wrapping#Overview|wrapping]] the sentinel error inside another error and using <code>[[Go_Error_Wrapping#errors.Is.28.29|errors.Is()]]</code> or <code>[[Go_Error_Wrapping#errors.As.28.29|errors.As()]]</code> to check for it.


=<tt>errors.As()</tt>=
In consequence, it improbably wise to avoid sentinel errors. There are a few cases when they are used in the standard library, but this is not a pattern you should emulate.


<font color=darkkhaki>TODO</font>
===Error Variable Naming===
Go [https://go.dev/talks/2014/names.slide#14 lore] recommends that the error variable names should be of the form <code>ErrSomething</code>:
<syntaxhighlight lang='go'>
var ErrSomething = fmt.Error("something")
</syntaxhighlight>
<font color=darkkhaki>The above documentation uses <code>SomethingError</code> format. Reconcile.</font>


=TO DEPLETE=
==Custom Error Types==
The error instances created with <code>[[#fmt.Errorf.28.29|fmt.Error()]]</code> and <code>[[#errors.New.28.29|errors.New()]]</code> carry context information in form of a detyped string. If we need to use context elements programmatically, during subsequent handling of the error, [[#Do_Not_Program_Around_Error_Message |parsing the string is discouraged]], as it can introduce brittleness. Custom error types is a better alternative, but it also comes with disadvantages, which will be discussed here: {{Internal|Go Custom Error Types#Overview|Custom Error Types}}


=<span id='Handling_Errors'></span>Error Handling=
Handling an error means inspecting the error value and making a decision. Always handle the error, never ignore it. When handling the error, [[#Only_Handle_an_Error_Once|only handle it once]]. Making more than one decision in response to a single error is bad. Logging an error is handling the error.


There are three choices when handling an error:
# [[#ignoring|ignore it]] by assigning to the blank identifier <code>[[Go_Language#The_Blank_Identifier_.28_.29|_]]</code> or "handling" it in an empty block - not recommended. However, if ignoring the error makes sense in a specific instance, also add a comment that explains '''why''' we are ignoring the error, to confirm to the code reader that ignoring the error is intentional and not a mistake.
# handle the error, [[#Dealing_with_Consequences_and_Continuing_the_Flow|deal with the consequences and continue the flow]], without returning the error the calling layer
# [[#Return_the_Error_to_the_Calling_Layer|return the error to the calling layer]]
==<span id='Dealing_with_Consequences_and_Continuing_the_Flow'></span>Deal with the Consequences and Continue the Flow==
When calling a function returning an error, always handle the error first.
<syntaxhighlight lang='go'>
r, err := someFunc()
if err != nil {
  // handle the error condition, ignore the rest of the results
  ...
}
</syntaxhighlight>
decision. Always handle the error. When handling the error, [[#Handling_the_Error_Twice_-_Bad|only handle it once]].


<font color=darkkhaki>
<span id='If_Syntax_for_Handling_Errors'></span>The "<code>if</code> with initialization statement" syntax supports this error handling idiom:
<syntaxhighlight lang='go'>
if err := someFunc(); err != nil {
  // handle the error condition
  return
}
// handle success
</syntaxhighlight>
For more details and advice on use see: {{Internal|Go_if#Statement_Precedes_Expression|<tt>if</tt> with initialization statement}}


==Return the Error to the Calling Layer==
The error can be returned to the calling layer [[#Unmodified|unmodified]], it can be [[#Annotated|annotated]] while being morphed into a different error, or it can be [[#Wrapping|wrapped]] into a new error while preserving its identity. Different situations call for different approaches, as discussed below.
===<span id='Unmodified'></span>Handle the Error as Opaque and Return It Unmodified===
<syntaxhighlight lang='go'>
func someFunc() error {
  ...
  err := someOtherFunc()
  if err != nil {

    return err

  }
  ...

}
</syntaxhighlight>
Handling the error as opaque and just returning it as function result is the most flexible error handling strategy because it requires the least coupling between your code and the caller. It is opaque because while you know that an error occurred, you don't introspect the error. This approach is appropriate when there is no additional context to be added to the error, and also we do not want to mark the error as a more specific type. <font color=darkkhaki>The problem with this approach is that the information where this error comes from is lost. Lost how? <span id='lost_information'></span>Update this section after researching [[#Stack_Traces|stack traces]].</font>


Create an error:
===<span id='Annotated'></span>Annotate the Error===
 
The error instance returned from the underlying layer can be "annotated" with a string according to the following pattern:
<syntaxhighlight lang='go'>
<syntaxhighlight lang='go'>
errors.New("at least three arguments are required")
err := someFunc()
if err != nil {

  return fmt.Errorf("some processing failed: %v", err)
}

</syntaxhighlight>
</syntaxhighlight>
The key is using <code>fmt.Errorf()</code> and the <code>[[Go_Package_fmt#.25v|%v]]</code> conversion character. The annotation process transforms the original error by creating a new error instance whose message is modified to add extra context, in this case "some processing failed". Note that the original error instance is discarded, along with its unique identity, and not embedded in the new error value. This pattern is incompatible with [[#Sentinel_Errors|sentinel error]] values and [[Go_Error_Wrapping#errors.Is.28.29|<code>errors.Is()</code>]] and type assertions because it converts the original error instance to a string, it merges it with another string, and then it converts the string back to a new error. This breaks the type information that might be otherwise passed. [[#Wrap_the_Error_Instance|Error wrapping]] should be preferred over error annotation when it comes to provide additional context around an error.


This approach is appropriate when we need to add more context to an error returned by the lower layer, while we do not care about preserving the error identity.


==External==
===<span id='Wrapping'></span>Wrap the Error Instance===
Go error mechanism allows "wrapping" error instances into other error instances, while preserving the wrapped error identity. This pattern supports building an error tree that is useful in providing deep context to an error, and it should be the '''preferred pattern''' to use when we want to provide additional context to an error.


* Go Specification - Handling panics https://golang.org/ref/spec#Handling_panics
There are two situation when wrapping is an appropriate approach: when providing more context to an error while preserving the error history, and also when we want to mark an error as a specific type. Error wrapping is discussed in detail here: {{Internal|Go_Error_Wrapping#Overview|Go Error Wrapping}}
==Overview==


Error are not types, they are interfaces.
==Only Handle an Error Once==
<span id='Handling_the_Error_Twice_-_Bad'></span>An error must be handled once. This is an example of handling an error twice:
<syntaxhighlight lang='go'>
result, err := someFunction()
if err != nil {
  // handling it once: we annotate the error and then send it to the log file
  log.Println("something went wrong: ", err)
  // handling it the second time: we send the unannotated error to the caller
  return err
}
</syntaxhighlight>
We log the annotated error and return the unannotated error to the caller, who possibly will log it, and return it, all the way back up to the top of the program. In the end, we get a stack of duplicate lines in the log file, and at the top of the program we get the original error without any context. Having more than one log line for a single error is a problem, because it makes debugging harder.


==The <tt>error</tt> Type==
Logging an error is handling the error. Logging the error and returning the same error is handling the error twice. We should log or return, never both.
==Concurrent Programming Error Handling==
This section discusses error handling in the context of concurrent programming with [[Go_Language_Goroutines#Concurrent_Programming_Error_Handling|goroutines]] and [[Go_Channels#Channels_and_Error_Propagation|channels]].


Go has a built-in <tt>error</tt> type. It is an interface. Its definition can be found in <tt>$GO_HOME/src/builtin/builtin.go</tt>. According to the documentation: the error built-in interface type is the conventional interface for representing an error condition, with the nil value representing no error.
In case of concurrent processing, the concurrent functions should send their errors to another part of the program that has complete information about the state of the program, and that can make more informed decision on what to do. A common pattern to implement this:
# Introduces a type that encapsulates the valid result '''and''' the error.
# Return the instances of that type on the channel that is supposed to carry the valid result.
# The reader on the channel, which is supposed to know what to do with the result, it is in a better position to decide what to do in case of error, in a richer context.
The instances of the <valid-result, error> type represent the complete set of possible outcomes of the concurrent code that produces the results, and allows us to separate the concern of handing the errors from that of producing the results. The goroutine that spawned the producer goroutine has a more complete context and can make more intelligent decisions about what to do with the errors.


===The "errors" Package===
=<span id='error'></span><span id='The_error_Type'></span>The <tt>error</tt> Type and <tt>error</tt> Values=
{{External|https://pkg.go.dev/builtin#error}}


* https://golang.org/pkg/errors
The <code>error</code> is a [[Go_Language#Pre-Declared_Types|pre-declared]] [[Go_Interfaces#Overview|interface]] type, part of the [[Go_Language#Universe_Block|universe block]], and constitutes the conventional interface for representing an error condition, with the <code>nil</code> value representing no error.


====Creating Custom <tt>error</tt> Instances====
<syntaxhighlight lang='go'>
 
type error interface {
=====<tt>errors.New()</tt>=====
  Error() string
 
Custom errors can be created by using the <tt>New()</tt> function in the "errors" package. Returns an <tt>error</tt> pointer:
 
<pre>
import "errors"
...
errPtr := errors.New("some text")
</pre>
 
=====<tt>fmt.Errorf()</tt>=====
 
The function <tt>[[Go_Package_fmt#fmt.Errorf.28.29|fmt.Errorf()]]</tt> also creates custom error instances.
 
==Exceptions==
 
Exceptions (in the Java sense) are not available in Go.
 
An alternative mechanism can be implemented by function returning multiple values, out of which one is an error indicator.
 
<font color=red><tt>panic</tt></font>
 
===<tt>defer</tt> and <tt>finally</tt>===
 
 
<tt>defer</tt> puts the function on the stack.
 
==Panics==
 
A ''panic'' generally indicates a programmer error (attempting to access an array index out of bounds, writing an uninitialized map, etc.) Panics can also be synthetically triggered with:
 
<pre>
panic(message)
</pre>
 
The panic can be caught on the stack outside the function that caused the panic, by executing <tt>recover()</tt>.  Recover can be called in any upper function on the stack, or even if the function that triggers the panic, if the invocation is made with <tt>[[Go Concepts - Functions#defer|defer]]</tt>.
 
 
The <tt>recover()</tt> built-in function allows a program to manage behavior of a panicking goroutine. Executing a call to recover inside a deferred function (but not any function called by it) stops the panicking sequence by restoring normal execution and retrieves the error value passed to the call of panic. If recover is called outside the deferred function it will not stop a panicking sequence. In this case, or when the goroutine is not panicking, or if the argument supplied to panic was nil, recover returns <tt>nil</tt>. Thus the return value from recover reports whether the goroutine is panicking.
 
==Error Handling Idioms==
 
The fact that Go function can return multiple value may be used in implementing a mechanism where a function returns the "good" value and an error value, instead of an exception. If an error occurs (the error instance is not <tt>nil</tt>), you should never trust the other values being returned by the function, they should always be ignored.
This is an idiomatic way of handling errors returned by functions. For a callee function A and a calling function B:
 
<pre>
// the "callee"
func A(...) (<business-result-type>, error) {
 
    var result <business-result-type>
 
    // processing ...
 
    // some error occurs
    return <zero-value-for-type>, errors.New("dang")
 
    // all good, make sure to return all values, including a nil error
    return result, nil
}
}
</syntaxhighlight>
The <code>Error()</code> method returns the string rendering of the error message. <span id='Error_Interface'></span>Errors in Go are instances that implement the <code>error</code> interface. When printing the error using methods like <code>fmt.Println()</code> the <code>Error()</code> method is automatically invoked.


// the calling function
Values of any type that implements the <code>error</code> interface can be used as error values and will be handled as error values by the Go runtime. <code>error</code> values are required to be more than just an error code, or a mere success/failure indicator.
func B(...) (<business-result-type>, error) {


    result, err := A(...)
=The <tt>errors</tt> Package=
{{External|https://pkg.go.dev/errors}}
The <code>[[Go Language Modularization#errors|errors]]</code> package contains functions for manipulating errors: creating new instances with <code>[[#errors.New.28.29|errors.New()]]</code>, checking for wrapped errors with <code>[[Go_Error_Wrapping#errors.Is.28.29|errors.Is()]]</code>,  <code>[[Go_Error_Wrapping#errors.As.28.29|errors.As()]]</code>, etc.


    if err != nil {
=<span id='Stack_Trace'></span>Stack Traces=
        return <zero-value-for-type>, err
    }


    // process the callee's returned result ...
A stack trace in form of a <code>[]byte</code> is generated with:
    // all good, make sure to return all values, including a nil error
    return result, nil
}
</pre>


The fact that functions return error values can be used in conjunction with the special <tt>if</tt> syntax:
<syntaxhighlight lang='go'>
import "runtime/debug"
...
ba := debug.Stack()
</syntaxhighlight>


<pre>
To print the stack trace at stderr:
if value, error := f(); err {
<syntaxhighlight lang='go'>
  // handle err
import "runtime/debug"
}
debug.PrintStack()
else {
</syntaxhighlight>
  // handle success
}
</pre>


More about multiple return values can be found here:
<font color=darkkhaki>Update [[#lost_information|above]].</font>
 
<blockquote style="background-color: #f9f9f9; border: solid thin lightgrey;">
:[[Go_Concepts_-_Functions#Multiple_Return_Values|Multiple Return Values]]
</blockquote>


=Errors and Logging=
<syntaxhighlight lang='go'>
err := fmt.Errorf("this is an error")
log.Printf("%v\n", err)
</syntaxhighlight>
prints:
<font size=-2>
2023/12/30 19:02:06 this is an error
</font>
<font color=darkkhaki>How do I display [[#Stack_Trace|stack traces]]?</font>


 
=<tt>panic</tt>=
</font>
{{Internal|Go panic|<tt>panic</tt>}}
=<tt>NotYetImplemented</tt> error=
For a typical implementation and usage, see [[#NotYetImplemented_Declaration|Sentinel Errors]].
=Error Patterns=
==Raw Errors and Well-Formed Errors==
{{Internal|Go Raw Errors and Well-Formed Errors#Overview|Raw Errors and Well-Formed Errors}}

Latest revision as of 17:15, 14 August 2024

External

Internal

TODO

  • Investigate github.com/hashicorp/go-multierror and morph the learnings into this article.

Overview

This article provides an overview of error handling in Go. It starts with error basics, where we explain that errors in Go are values, and functions return them as results. We continue by enumerating the ways error instances can be created and how the caller of a function that returns an error is supposed to handle it. We also discuss how to handle errors in concurrent programming.

Error Basics

A big part of programming is how you handle errors. Errors in Go are values of the error type, not control structures. Java and Python's error handling mechanisms rely on exceptions, which are program flow control structures. Go is different, in that is has no exceptions, which implies that any error condition that you will encounter in a program is by definition not exceptional, but part of the normal flow of the program. This approach makes the statement that error handling is important, and as we develop our programs, we should give our error paths the same attention we give our algorithms. The closest thing to exceptions in Go is the panic mechanism.

When an error condition occurs, which usually indicates that the system has entered in a state in which it cannot fulfill an operation that a user explicitly or implicitly requested, the function in question must return an error value in addition to the result via its support for multiple return values. The error is typically returned as the first and only argument, if the function does not return anything else, or the last argument otherwise:

func someFunc(...) error {
  ...
  err := ...
  return err
}
func someFunc(...) (result SomeType, [...], error) {
  ...
  err := ...
  return ...,...,err
}

Note that the function might return a concrete type of an error instead of the error interface, but this is usually a mistake. Returning the interface should be preferred.

If an error occurs, and the error value returned by the function is non-nil, all other values returned by the function must be ignored.

If no error occurs, the function must return nil as error value. In Go, nil means "no error".

When the error is created, it is the error implementation’s responsibility to summarize the context in the error message. The error string should start with a lowercase character, unless it begins with names that require capitalization. Error strings, unlike comments, should not end with punctuation marks. Error messages should be descriptive, yet compact. It should be easy to understand what exactly went wrong by reading the error message. Descriptive elements in the message help the operators of the program to figure out what caused the error. For example, if a file cannot be opened because of lack of permission, the error message should be "open /some/path/myfile.txt: permission denied", not just "permission denied".

The caller of the function is supposed to handle the error. An error returned by a function can be ignored if the caller assigns it to the blank identifier _, but this approach is discouraged. Ignoring an error leads in most cases to bad outcomes. Handling an error means inspecting the error value and making a decision. Always handle the error. When handling the error, only handle it once.

The error message can be introspected by invoking Error() on the error instance. However, the result of this introspection should not be used in the program logic. That output is aimed at humans operating the program, not code. The contents of that string belong in a log file or displayed on the screen. You should not try to change the behavior of the program as result of processing the error message. If you need more context carried within the error instance, look into custom error types.

A valid pattern to handle an error is augmenting the error with more context and returning it to the upper layer. When possible, simply return the error unchanged. If you must add more context, prefer error wrapping over annotation.

Another error handling pattern is built around the fact that all errors belong to one of two categories: bugs and known edge cases (disk full, network failure, etc.). The pattern requires to represent the known edge cases as "well-formed" errors and declare them as part of our component API. All other errors are "raw errors" and represent bugs. When exported, errors become part of your package's public API and must be treated with as much care as you would any other part of your public API. This pattern is described here: Raw Errors and Well-Formed Errors.

Rob Pike's Go proverbs concerning errors: "Errors are just values" and "Don't just check errors, handle them gracefully". Also "error values in Go aren’t special, they are just values like any other, and so you have the entire language at your disposal".

Also see:

Go Style

Error Instance Creation

fmt.Errorf()

err := fmt.Errorf("some %s error", "blue")

The most common way to generate an error instance is fmt.Errorf(). fmt.Errorf() is equivalent to errors.New(), in that it produces a pointer to a new error instance, but it also allows formatting the error text using the capabilities of the fmt package. For this reason, fmt.Errorf() allows more flexibility and should be preferred over errors.New(). fmt.Errorf() invokes errors.New() internally. Each call to fmt.Errorf() returns a distinct error value even if the text is identical.

Error Wrapping

A new error instance can be created by wrapping an existing error with fmt.Errorf(). Error wrapping is an error handling idiom discussed in detail in Wrap the Error Instance below.

errors.New()

err := errors.New("some error")

The errors package New() function returns a pointer to a new error instance *errors.errorString that carries the given text. errorString is an unexported type of the errors package. Each call to New() returns a distinct error value even if the text is identical. The New() function does not have string formatting capabilities. The equivalent function fmt.Errorf() does and it should be preferred over errors.New().

Sentinel Errors

Sentinels are a special category of errors that are specifically intended to be identifiable using an equality test, which implies that only one error instance is used throughout the runtime. The name comes from the practice in computer programming of using a specific value to signify that no further processing is possible. In this case, we use specific values to signify an error, and we check for the presence of that value with the equality == operator or with errors.Is() (preferred). A sentinel error instance must have a unique identity, to make the evaluation with the equality test possible. An example of a sentinel error is io.EOF:

n, err := r.Read(buf)
if err == io.EOF {
 ...
}

The typical way to declare and use a sentinel error:

.
├── internal
│    ├── errs
│    │    ├── errs.go

The package name is designed to avoid clashing with the built-in type error, with the errors package name and with the commonly used variable name err.

package errs

...

var NotYetImplemented = fmt.Errorf("not yet implemented")
var NotFound = fmt.Errorf("not found")

If it is not practical to create a new errs package and the errors must be declared in an existing package, it is OK to postfix the error instance name with Error:

package something
...
var NotYetImplementedError = fmt.Errorf("not yet implemented")
var NotFoundError = fmt.Errorf("not found")

To check for occurrence, prefer errors.Is() over type assertions. errors.Is() is designed to work with both individual errors and wrapped errors.

func someFunc() error {
  return errs.NotYetImplemented
}

...

err := someFunc()
if errors.Is(err, errs.NotYetImplemented) {
  fmt.Printf("someFunc() is not yet implemented")
}

There are a few disadvantages to using sentinel errors.

The most serious is that the sentinel errors create a dependency between packages. For example, to check if an error is equal with io.EOF, you must import the io package. This particular example is not that bad, because io is quite common, but if this kind of coupling generalizes, it can become problematic. Using sentinel errors as part of the internal packages of a module and not exporting them as part of the public interface of the module is probably OK.

Another is that an attempt to annotate the sentinel error to provide more context would cause returning a different error would break the equality check. This is a limitation that can be worked around by wrapping the sentinel error inside another error and using errors.Is() or errors.As() to check for it.

In consequence, it improbably wise to avoid sentinel errors. There are a few cases when they are used in the standard library, but this is not a pattern you should emulate.

Error Variable Naming

Go lore recommends that the error variable names should be of the form ErrSomething:

var ErrSomething = fmt.Error("something")

The above documentation uses SomethingError format. Reconcile.

Custom Error Types

The error instances created with fmt.Error() and errors.New() carry context information in form of a detyped string. If we need to use context elements programmatically, during subsequent handling of the error, parsing the string is discouraged, as it can introduce brittleness. Custom error types is a better alternative, but it also comes with disadvantages, which will be discussed here:

Custom Error Types

Error Handling

Handling an error means inspecting the error value and making a decision. Always handle the error, never ignore it. When handling the error, only handle it once. Making more than one decision in response to a single error is bad. Logging an error is handling the error.

There are three choices when handling an error:

  1. ignore it by assigning to the blank identifier _ or "handling" it in an empty block - not recommended. However, if ignoring the error makes sense in a specific instance, also add a comment that explains why we are ignoring the error, to confirm to the code reader that ignoring the error is intentional and not a mistake.
  2. handle the error, deal with the consequences and continue the flow, without returning the error the calling layer
  3. return the error to the calling layer

Deal with the Consequences and Continue the Flow

When calling a function returning an error, always handle the error first.

r, err := someFunc()
if err != nil {
  // handle the error condition, ignore the rest of the results
  ...
}

decision. Always handle the error. When handling the error, only handle it once.

The "if with initialization statement" syntax supports this error handling idiom:

if err := someFunc(); err != nil {
  // handle the error condition
  return
}
// handle success

For more details and advice on use see:

if with initialization statement

Return the Error to the Calling Layer

The error can be returned to the calling layer unmodified, it can be annotated while being morphed into a different error, or it can be wrapped into a new error while preserving its identity. Different situations call for different approaches, as discussed below.

Handle the Error as Opaque and Return It Unmodified

func someFunc() error {
  ...
  err := someOtherFunc()
  if err != nil {
    return err
  }
  ...
}

Handling the error as opaque and just returning it as function result is the most flexible error handling strategy because it requires the least coupling between your code and the caller. It is opaque because while you know that an error occurred, you don't introspect the error. This approach is appropriate when there is no additional context to be added to the error, and also we do not want to mark the error as a more specific type. The problem with this approach is that the information where this error comes from is lost. Lost how? Update this section after researching stack traces.

Annotate the Error

The error instance returned from the underlying layer can be "annotated" with a string according to the following pattern:

err := someFunc()
if err != nil {
   return fmt.Errorf("some processing failed: %v", err)
}

The key is using fmt.Errorf() and the %v conversion character. The annotation process transforms the original error by creating a new error instance whose message is modified to add extra context, in this case "some processing failed". Note that the original error instance is discarded, along with its unique identity, and not embedded in the new error value. This pattern is incompatible with sentinel error values and errors.Is() and type assertions because it converts the original error instance to a string, it merges it with another string, and then it converts the string back to a new error. This breaks the type information that might be otherwise passed. Error wrapping should be preferred over error annotation when it comes to provide additional context around an error.

This approach is appropriate when we need to add more context to an error returned by the lower layer, while we do not care about preserving the error identity.

Wrap the Error Instance

Go error mechanism allows "wrapping" error instances into other error instances, while preserving the wrapped error identity. This pattern supports building an error tree that is useful in providing deep context to an error, and it should be the preferred pattern to use when we want to provide additional context to an error.

There are two situation when wrapping is an appropriate approach: when providing more context to an error while preserving the error history, and also when we want to mark an error as a specific type. Error wrapping is discussed in detail here:

Go Error Wrapping

Only Handle an Error Once

An error must be handled once. This is an example of handling an error twice:

result, err := someFunction()
if err != nil {
  // handling it once: we annotate the error and then send it to the log file
  log.Println("something went wrong: ", err)
  // handling it the second time: we send the unannotated error to the caller
  return err
}

We log the annotated error and return the unannotated error to the caller, who possibly will log it, and return it, all the way back up to the top of the program. In the end, we get a stack of duplicate lines in the log file, and at the top of the program we get the original error without any context. Having more than one log line for a single error is a problem, because it makes debugging harder.

Logging an error is handling the error. Logging the error and returning the same error is handling the error twice. We should log or return, never both.

Concurrent Programming Error Handling

This section discusses error handling in the context of concurrent programming with goroutines and channels.

In case of concurrent processing, the concurrent functions should send their errors to another part of the program that has complete information about the state of the program, and that can make more informed decision on what to do. A common pattern to implement this:

  1. Introduces a type that encapsulates the valid result and the error.
  2. Return the instances of that type on the channel that is supposed to carry the valid result.
  3. The reader on the channel, which is supposed to know what to do with the result, it is in a better position to decide what to do in case of error, in a richer context.

The instances of the <valid-result, error> type represent the complete set of possible outcomes of the concurrent code that produces the results, and allows us to separate the concern of handing the errors from that of producing the results. The goroutine that spawned the producer goroutine has a more complete context and can make more intelligent decisions about what to do with the errors.

The error Type and error Values

https://pkg.go.dev/builtin#error

The error is a pre-declared interface type, part of the universe block, and constitutes the conventional interface for representing an error condition, with the nil value representing no error.

type error interface {
  Error() string
}

The Error() method returns the string rendering of the error message. Errors in Go are instances that implement the error interface. When printing the error using methods like fmt.Println() the Error() method is automatically invoked.

Values of any type that implements the error interface can be used as error values and will be handled as error values by the Go runtime. error values are required to be more than just an error code, or a mere success/failure indicator.

The errors Package

https://pkg.go.dev/errors

The errors package contains functions for manipulating errors: creating new instances with errors.New(), checking for wrapped errors with errors.Is(), errors.As(), etc.

Stack Traces

A stack trace in form of a []byte is generated with:

import "runtime/debug"
...
ba := debug.Stack()

To print the stack trace at stderr:

import "runtime/debug"
debug.PrintStack()

Update above.

Errors and Logging

err := fmt.Errorf("this is an error")
log.Printf("%v\n", err)

prints:

2023/12/30 19:02:06 this is an error

How do I display stack traces?

panic

panic

NotYetImplemented error

For a typical implementation and usage, see Sentinel Errors.

Error Patterns

Raw Errors and Well-Formed Errors

Raw Errors and Well-Formed Errors