Journal

Writing down the things I learned. To share them with others and my future self.

11 May 2020

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:

1
func Compile(expr string) (*Regexp, error)

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package main

import (
    "log"
    "regexp"
)

func main() {
    exp := "foo.*"
    input := []byte("foobar")
    r, err := regexp.Compile(exp)
    if err != nil {
        log.Fatal("Failed to compile expression")
    }
    log.Printf("%v matched: %v", input, r.Match(input))
}

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package main

import (
    "log"
    "regexp"
)

func main() {
    exp := "foo.*"
    input := []byte("foobar")
    r := regexp.MustCompile(exp)
    log.Printf("%v matched: %v", input, r.Match(input))
}

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:

1
func Must(t *Template, err error) *Template

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:

1
var t = template.Must(template.New("name").Parse("text"))

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package main

import (
	"fmt"
)

func sub(a, b int) int {
	return a - b
}

func add(a, b, c int) int {
	return a + b + c
}

func main() {
	res := add(sub(1, 2), 3)
	fmt.Printf("(1-2)+3=%d\n", res)
}

The error message is:

1
2
3
./prog.go:16:12: not enough arguments in call to add
	have (int, number)
	want (int, int, int)

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.

Tags