go generate

Learn how to use go generate for automatic code generation, with practical examples using stringer and mockgen.

Imagine you have a type that represents a set of constants, and you need a human-readable string for each one. Writing those strings by hand is tedious and error‑prone. The go generate command automates this kind of repetitive work by running external tools that you declare inside your own source files. It is not a code generator itself — it is a runner for generators.

Why Code Generation Matters

When you maintain code that keeps multiple representations of the same data, you create a synchronization problem. For example, a set of integer constants and their string names must always match. Manually keeping them in sync leads to bugs: a new constant might be added but the string list forgotten, or a rename changes the meaning silently. Code generation solves this by deriving one representation from another automatically.

Go’s ecosystem takes this further. Because the language avoids runtime reflection for many tasks, generation fills the gap. Stringer for enums, mockgen for test doubles, protobuf compilers for gRPC — all rely on generation. go generate gives these tools a uniform, discoverable way to hook into your project.

How go generate Works

Under the hood, go generate scans Go source files for special comments that start with //go:generate. It then executes the commands that follow those comments, one at a time, in the order they appear. The commands run in the package directory, and go generate sets several environment variables that generators can use:

  • $GOFILE – the base name of the source file containing the directive.
  • $GOPACKAGE – the name of the package.
  • $GOARCH, $GOOS – the target architecture and operating system.
  • $GOLINE – the line number of the directive in the source file.
  • $DOLLAR – a literal dollar sign (useful in shell commands embedded inside directives).

go generate does not run automatically when you build or test. You must invoke it explicitly, usually when you change the definitions that the generated code depends on.

Part of the development workflow:

go generate is designed to be run manually or as part of a script. Generated files are normally committed to version control so that consumers of your package don’t need the generators installed.

Writing a Generate Directive

A directive is a single line comment with no leading whitespace before //go:generate:

//go:generate command argument1 argument2 ...

The command can be any executable that go generate can find in your PATH. Arguments are separated by spaces. This line works:

//go:generate stringer -type=Pill

It invokes the stringer tool with the flag -type=Pill.

Because go generate runs commands directly (not through a shell), shell operators like >, |, and && do not work. If you need a pipeline or output redirection, wrap the whole command with sh -c:

//go:generate sh -c "go tool cgo -godefs ctypes.go > types.go"

No shell interpretation:

Forgetting that go generate skips the shell is the most common mistake with directives. If your command uses redirection or pipes and you omit sh -c, the command will either fail or produce unexpected results.

You can place multiple directives in one file. They run in the order they appear.

Running go generate

With no arguments, go generate processes the package in the current directory. To process all packages under your module, use:

go generate ./...

Use the -v flag to see each command as it runs:

go generate -v ./...

If you just want to see what would run without executing anything, add -n:

go generate -n ./...

Common flags:

FlagPurpose
-vPrint commands as they are executed.
-nDry run – print commands but do not execute them.
-xPrint commands as they are executed (like -v) but also show the expanded arguments.
-runRun only directives whose command matches the given regular expression.

Using stringer for Enum String Methods

One of the most common code generation tasks is providing a String() method for a set of named constants. The stringer tool, part of the golang.org/x/tools repository, does exactly that.

Installing stringer

go install golang.org/x/tools/cmd/stringer@latest

Ensure that $GOPATH/bin is in your PATH so go generate can find it.

Adding the directive

Suppose you have a file pill.go that defines a medication type:

package painkiller
//go:generate stringer -type=Pill
type Pill int
const (
    Placebo Pill = iota
    Aspirin
    Ibuprofen
    Paracetamol
)

The directive tells stringer to generate a String() method for the Pill type.

Running the generator

Now run go generate in the package directory:

$ go generate

A new file named pill_string.go appears.

// Code generated by "stringer -type=Pill"; DO NOT EDIT.
package painkiller
import "strconv"
func _() {
    // An "invalid array index" compiler error signifies that the constant values have changed.
    // Re-run the stringer command to generate them again.
    var x [1]struct{}
    _ = x[Placebo-0]
    _ = x[Aspirin-1]
    _ = x[Ibuprofen-2]
    _ = x[Paracetamol-3]
}
const _Pill_name = "PlaceboAspirinIbuprofenParacetamol"
var _Pill_index = [...]uint8{0, 7, 14, 23, 34}
func (i Pill) String() string {
    if i < 0 || i >= Pill(len(_Pill_index)-1) {
        return "Pill(" + strconv.FormatInt(int64(i), 10) + ")"
    }
    return _Pill_name[_Pill_index[i]:_Pill_index[i+1]]
}

Check the generated output:

If you see pill_string.go with the String() method, the generation worked correctly.

The generated file includes a compile‑time guard that breaks if you change the constant values without re‑running stringer. That’s why the _() function appears: it indexes into an array with expressions like Placebo-0. If Placebo no longer equals 0, the index becomes invalid and the code won’t compile, forcing you to regenerate.

Never edit generated files by hand:

Manually editing pill_string.go is tempting when you want a quick fix, but the next go generate run will silently overwrite your changes. Always modify the source (the constant definitions or the generator invocation) and regenerate.

Using mockgen for Interface Mocks

For testing, you often need a mock implementation of an interface. The mockgen tool from github.com/golang/mock generates a full mock from an interface definition.

Installing mockgen

go install github.com/golang/mock/mockgen@latest

Preparing the source interface

Create a file fetcher.go with an interface:

package api
// Fetcher retrieves data from a remote service.
type Fetcher interface {
    Fetch(id string) ([]byte, error)
}

Add a directive that tells mockgen to read this source file and write a mock implementation:

//go:generate mockgen -source=fetcher.go -destination=mocks/fetcher_mock.go -package=mocks

This creates the mock in a mocks subdirectory.

Running the generator

Before running go generate, create the mocks directory — mockgen does not create it for you:

mkdir -p mocks
go generate

After running, mocks/fetcher_mock.go contains a MockFetcher struct with methods like EXPECT() and assertions for Fetch.

The generated mock handles method calls, argument matching, and return values so you can use it directly in tests:

import (
    "testing"
    "github.com/golang/mock/gomock"
    "yourmodule/api/mocks"
)
func TestFetch(t *testing.T) {
    ctrl := gomock.NewController(t)
    defer ctrl.Finish()
    mock := mocks.NewMockFetcher(ctrl)
    mock.EXPECT().Fetch("123").Return([]byte("data"), nil)
    // Use `mock` wherever an api.Fetcher is expected.
}

Destination directories must exist:

mockgen (and many generators) will fail if the output directory doesn’t exist. Always create the directory before running go generate, or include a mkdir command in a shell‑wrapped directive if you need automation.

Things That Can Go Wrong

The generator is not installed

When go generate can’t find the command in PATH, it stops with an error. Install the tool with go install and verify that $GOPATH/bin is in your PATH.

Shell operators are used without sh -c

A directive like //go:generate echo "hello" > output.txt fails because > is not interpreted. Use sh -c to wrap the full shell command.

Generated files are edited manually

Changes to generated files are lost the next time you run go generate. If you need different behavior, either configure the generator or write a custom wrapper.

Stale generated files

If you forget to run go generate after changing the source types, the generated code becomes inconsistent with the definitions. Many generators include compile‑time guards to catch this, but not all. Make regenerating a habit before running tests or building.

Build tags in the same file

If your source file has build tags, they apply to the //go:generate directive as well. A directive in a file with //go:build ignore will never run. Keep directives in files that are always compiled, or use a separate file without build constraints specifically for generation.

Summary

go generate is not itself a code generator — it’s a consistent, scriptable way to run generators that you declare inside your source code. This turns boilerplate‑heavy tasks like stringer methods and mocks into a single command that stays tightly coupled to the types they describe.

The design reinforces the Go philosophy of explicitness. Generated files are checked in, so the downstream user needs no extra tools. Generation happens when the developer decides, not automatically, keeping the build fast and the process transparent.

If you are maintaining a package that exposes enums or interfaces, adding the appropriate //go:generate directive makes the codebase self‑documenting and easier to keep correct. The next time you add a constant and forget to update the string mapping, stringer will catch it — not your future self during a late‑night debugging session.