skip to content

What does regexp.MustCompile do differently from regexp.Compile, and where should it be called?

level: middleimportance: should knowfreq 62%

answer

  1. same work, no error return
  2. the prefix is a naming convention
  3. ask where the argument came from
  4. literal in source versus data from outside
  5. fails at process start, not mid-request

basics

~20 s

regexp.Compile returns (*Regexp, error); regexp.MustCompile returns only the *Regexp and panics if the pattern is malformed. Use it where the pattern is a literal in your source, typically a package-level var or init, never on caller or config input.

solid answer

~50 s

`regexp.Compile` returns `(*regexp.Regexp, error)`; `regexp.MustCompile` takes the same argument, returns just the `*regexp.Regexp`, and panics if the pattern will not parse. The `Must` prefix is a naming convention across the standard library — `template.Must` is the general form, taking the value-and-error pair a `Parse` call returns. Panicking is acceptable there because the argument is a literal the programmer wrote: a bad pattern is a build bug, no caller could handle it, and package-level variables are initialised before `main`, so the failure lands at process start and in CI. The moment the pattern comes from a config file, a request or an environment variable it is data, and data is allowed to be wrong, so you switch to `Compile` and return a wrapped error. The `Must` prefix is a promise about the call site, not about the value.

code

go · 11 lines
go
// Fine: the pattern is a literal in this file, so a typo fails at process start.
var ruleNamePattern = regexp.MustCompile(`^[a-z][a-z0-9-]*$`)

// Not fine as MustCompile: the pattern comes from a config file an operator edits.
func compileOperatorRule(pattern string) (*regexp.Regexp, error) {
	re, err := regexp.Compile(pattern)
	if err != nil {
		return nil, fmt.Errorf("bad rule pattern %q: %w", pattern, err)
	}
	return re, nil
}

go deeper

for a junior

Remember the pair: Compile returns a value and an error, MustCompile returns the value alone and panics on a bad pattern. Recall that it belongs next to a package-level var declaration.

for a middle

Explain why the panic is acceptable there — the argument is a source literal, so the failure is a build bug that surfaces before main runs — and name template.Must as the general value-and-error form.

for a senior

Show the failure mode you have seen: a Must call whose argument became dynamic, turning an operator typo into a crash while serving. Describe how you catch it and what you replace it with.

for a principal

Turn it into a rule others can apply without you: where Must calls are permitted, who reviews an exception, and why the grep-able convention beats case-by-case judgment.

### The convention `Must` is a naming convention in Go, not a language feature. A function whose name begins with `Must` does the same job as its plain sibling, but instead of returning an error it **panics** if the operation fails. It exists so that a value which is guaranteed correct at compile time can be assigned to a package-level variable, where there is no place to put an `if err != nil`. The two shapes you will meet: ```go func MustCompile(str string) *Regexp // regexp func Must(t *Template, err error) *Template // text/template and html/template ``` `regexp.MustCompile` takes the same argument as `regexp.Compile` and returns only the `*regexp.Regexp`, panicking if the pattern does not parse. `template.Must` is the more general form: it takes the `(value, error)` pair that a call such as `template.New("page").Parse(src)` returns, panics if the error is non-nil, and otherwise hands back the value. Both give you an expression that can appear on the right-hand side of a `var` declaration. ### Why panicking is acceptable here Look at what the argument actually is: ```go var ruleNamePattern = regexp.MustCompile(`^[a-z][a-z0-9-]*$`) ``` The pattern is a **literal written by the programmer**. If it is malformed, the code is wrong, and it is wrong for every run of the program, every user and every input. There is no error handling a caller could usefully perform — you cannot "retry" a typo. And because package-level variables are initialised before `main` runs, the panic happens at process start, in your own tests and in CI, long before a request arrives. That is the whole bargain: **a Must helper converts a programmer mistake into an immediate, loud, unmissable startup failure.** The same reasoning covers a `func init()` that parses embedded templates, or a `var` holding a parsed time layout. The value is fixed at build time; failing to build it is a build bug. ### Where it stops being acceptable The moment the argument stops being a literal, the reasoning collapses. A pattern read from a configuration file, a query parameter, a database column or an environment variable is **data**, and data is allowed to be wrong. Passing it to `regexp.MustCompile` means an operator's typo in a config file crashes the process — and if the call sits inside an HTTP handler or a worker, it crashes the process *while serving*, with a stack trace instead of a message telling the operator which rule is broken. So the discipline is a call-site rule, not a function rule: - Package-level `var`, `func init`, and tests: `Must` is fine and idiomatic. - Anywhere the argument came from outside the source file: use the plain `Compile` / `Parse` form, wrap the error with context, and return it. Tests are worth calling out. Inside a `_test.go` file the whole point is to fail loudly, and helpers that panic (or call `t.Fatal`) are fine — the "process" that dies is the test binary. ### Writing your own With generics the general helper is three lines, and many codebases have one: ```go func Must[T any](v T, err error) T { if err != nil { panic(err) } return v } ``` If you export such a helper, document it the way the standard library does — that it is intended for variable initialisation with values known to be valid — because the name alone is what tells a reader "this panics". A reviewer's shortcut is exactly that: **the `Must` prefix is a promise about the call site, not about the value.** Seeing `Must` anywhere near caller-supplied data is the signal to push back. ### The trap in review The characteristic defect is a refactor that *moves* a working `MustCompile` from a package-level `var` into a function so it can take a parameter. The diff looks harmless — the same call, one level in — but the argument's provenance changed, and the crash moved from startup into the request path. When you see that in a pull request, the fix is to compile once at start-up if the pattern is static, or to switch to `regexp.Compile` and return an error if it is not.

  • What arguments does template.Must take, and why is its shape different?
    `template.Must(t *Template, err error) *Template` takes the pair that a call such as `template.New("page").Parse(src)` returns, panics if the error is non-nil, and otherwise returns the template. That shape wraps any function returning a value and an error, so it works in a var declaration where no `if err != nil` can go.
  • How would you write a Must helper for your own package?
    With generics it is three lines: `func Must[T any](v T, err error) T { if err != nil { panic(err) }; return v }`. If you export it, document that it is meant for variable initialisation with values known valid at build time — the name is what warns a reader it panics.
  • Is a Must helper acceptable inside test files?
    Yes, freely. The process that dies is the test binary, and failing loudly is the point of a test. Setup helpers in `_test.go` files commonly panic or call `t.Fatal` rather than threading errors through fixture code.

saying these in an interview costs you the question

  • Thinks Must is a language keyword rather than a naming convention
  • Calls MustCompile on a pattern read from configuration
  • Recompiles the same literal pattern inside a hot function
  • Believes MustCompile returns nil on a bad pattern
  • Moves a MustCompile from a package-level var into a request path