On Errors in Golang
In Golang are two ways to signal an error in a function or method. The first is the convention to return an error
value
as last return parameter of a function or method. The position of the return parameter is only a convention, but I’m not
aware of any library not using this convention. The error
type is a builtin interface.
The second way to signal an error is raising a panic. A panic have to be catched via the recover
method in a defer block
in the call stack. If the panic is not catched it will crash the program. This sounds like exceptions in languages like Java,
but panics should only be used if there is something unexpected and non-recoverable. In general, panics should only be
recovered to post them services like sentry.io. After delivering the panic to your observability stack,
the program should be terminated. This blog post goes into the details
of panics, defer and recover.
As described in the first paragraph, the convention is to return an error if the function can return an error. Since it is
a good practice to check every error go code has a lot of if err != nil
statements. There are a lot of people out there
hating that pattern. I got used to it. Now it shapes my way of thinking. Before doing anything with the return value of
function I think about the error cases. What should the code do in case of an error? Abort? Does the code have to clean up
any resources? Rollback any transaction? Can the code recover safely from the error? Yes this distracts from the core
business logic in the first place, but I think it leads overall to a more robust code.
The Must Pattern
However, always checking for that possible error can be annoying in cases where the function returns an error, but I know
at time of programming there will never be an error. Or, if there is an error, some fundamental assumptions broke.
A good example is the Compile function from the regexp
package. This function
has the following signature:
|
|
The function tries to compile the given string as regular expression. If the string is not a valid regular expression an error is returned. So far, the usual method signature. There is a possible error, hence return error. In the following example, the error check is never true, since I know in advance that the static expression is always a valid expression.
|
|
The regexp
package provides us a handy wrapper around Compile
. It is called MustCompile
and returns only a *Regexp
.
If Compile
returns an error not equals nil, MustCompile
panics. This reduces the above code to:
|
|
Personally I use MustCompile
only if I know the regular expression at compile time. If my program fails to parse the
regular expression, something fundamental in the regexp
library has changed. If it depends on user input you should
never use MustCompile
since the user may mess up your regular expression.
There are other packages providing a methods and function with a Must
prefix. E.g. MustRegister
from the prometheus client library, or MustParse from the uuid
package.
All those functions panic instead of returning an error. All those functions should only be used if you know for sure an
error can’t happen. Vice versa, the Must
prefix of a function indicates the user of your package that your function will
panic on error. I made it a rule of thumb for myself to prefix every function with Must
if I implement a panic in that
function.
If your package API has many functions returning the same type, and an error you can copy the Must
-wrapper pattern
from the template package. This wrapper has the following signature:
|
|
It accepts a Template and an error. In case err!=nil
it will panic. The example for this function shows a lesser known
feature of golang. Example:
|
|
template.New("name")
returns a Template
and an error. Since Must
accept exactly those two arguments, you can just
pass the output of template.New
into Must
. This saves a lot of Must
prefixed functions in the API of the template
package. Sadly, the following example will not compile:
|
|
The error message is:
|
|
If you want to pass multiple return values of a function directly into another function, the receiving function must have exactly the same input values.
In conclusion, use the prefix Must
if your method or function panics. Be careful if an API provides a Must
function.
Expect that function to panic.