Skip to content
gohacks.dev
Basics

Must Functions in Go and Where They Belong in a Codebase

The Go Must convention wraps a call that returns an error and panics on failure. Learn where it belongs, write a generic Must, and see where it does harm.

3 min read
Summarize in AI
Flat illustration of an editor window full of code.

Some calls cannot fail unless the programmer made a mistake. A regular expression written as a string constant is either valid or it is not, and you find out the first time the program runs. Handling that error with if err != nil produces code nobody can reach in production and nobody can recover from sensibly. Go's answer is the Must convention: a function that does the same job as the error-returning one and panics when the error is non-nil.

Tested with Go 1.25.6, macOS on Apple Silicon (arm64)

It is a narrow tool with a clear home. This note says what the home is. It is a companion to panic or error and part of the error handling guide. Everything ran on Go 1.25.6.

The standard examples

The standard library has several, and they all share a shape: a Compile or Parse that returns (T, error) and a MustCompile or Must that returns only T.

main.goGo
package main

import (
	"fmt"
	"os"
	"regexp"
	"text/template"
)

var (
	slug  = regexp.MustCompile(`^[a-z0-9]+(-[a-z0-9]+)*$`)
	greet = template.Must(template.New("greet").Parse("Hello, {{.Name}}!\n"))
)

func main() {
	fmt.Println(slug.MatchString("go-must-function"), slug.MatchString("Not A Slug"))
	greet.Execute(os.Stdout, struct{ Name string }{"gopher"})
}
Text
true false
Hello, gopher!

Both package-level variables are initialised before main runs. If the pattern or the template had a typo, the program would panic at start-up with a message pointing at the problem, on every run and on every machine. That is better than discovering it in a rarely used code path months later. regexp.Compile exists for patterns that come from users or configuration, where a bad pattern is a normal event to report.

Writing your own

Since generics, one helper covers any call shaped like (T, error):

main.goGo
package main

import (
	"fmt"
	"net/url"
	"time"
)

func Must[T any](v T, err error) T {
	if err != nil {
		panic(err)
	}
	return v
}

var (
	apiBase = Must(url.Parse("https://api.example.com/v1/"))
	utc8    = Must(time.LoadLocation("Asia/Singapore"))
)

func main() {
	fmt.Println(apiBase.Host, utc8)
}
Text
api.example.com Asia/Singapore

The standard library's own proposed Must has been discussed more than once and not added, in part because it invites the misuse below. Keep your copy in an internal package, name it clearly, and use it for initialisation only.

Where it belongs

  • Package-level variables built from constants: patterns, templates, parsed URLs, embedded assets.
  • main or init where failure means the program cannot start, such as parsing a required configuration file whose absence is a deployment error and not a user mistake.
  • Tests, where a failure to set up a fixture should abort the test with a clear message. Prefer t.Fatal when you have a *testing.T; use Must in package-level test data.

Where it does harm

The failure mode is using it on data that varies. Must(strconv.Atoi(r.FormValue("n"))) in a handler turns a bad query string into a panic, and the server's recover turns that into a 500 and a stack trace for what should have been a 400. Anything read from a file, a request, a database or the network can fail for ordinary reasons and has to return an error. A second bad use is inside library code: a library that panics on a bad argument from its caller forces every caller to read the source to know. If a library offers a Must form, it should offer the error-returning one beside it, as regexp does.

For the broader rule about when panics are acceptable, see panic or error, and for the standard examples the documentation of regexp.MustCompile and template.Must.

Taggedquick tip
Share

Keep reading

Get new articles by email

We send one email when a new article is published. No spam.

Unsubscribe in one click. No spam, ever.