Menu
Coddy logo textTech

Code Generation Overview

Part of the Object Oriented Programming section of Coddy's GO journey — lesson 84 of 107.

Code generation is an alternative to reflection that creates Go source code at compile time rather than inspecting types at runtime. This approach gives you type safety and better performance while still reducing repetitive boilerplate code.

Go provides the go generate command to automate code generation. You place a special comment directive in your source file, and running go generate executes the specified tool:

//go:generate stringer -type=Status

type Status int

const (
    Pending Status = iota
    Active
    Completed
)

Running go generate ./... invokes the stringer tool, which creates a new file containing a String() method for the Status type. The generated code is regular Go that gets compiled with your program.

Common code generation tools in the Go ecosystem include stringer for enum string methods, mockgen for creating test mocks from interfaces, and protoc for generating code from Protocol Buffer definitions. Many ORMs and API frameworks also use generation to create type-safe database queries or client code.

The key advantage over reflection is that generated code is checked by the compiler. Errors are caught at build time, not runtime. The trade-off is an extra build step and generated files to manage in your repository. For OOP patterns requiring repetitive implementations across many types, code generation often provides the best balance of safety and convenience.

challenge icon

Challenge

Easy

Let's build a code generator that creates String() methods for enum-like types! While we can't run actual go generate in this environment, you'll simulate what code generation tools do—producing Go source code programmatically based on type definitions.

You'll organize your code across two files:

  • generator.go: Create your code generation logic.

    Build a EnumType struct that represents an enum-like type definition with a Name (string) and Values (slice of strings representing the constant names).

    Create a function GenerateStringMethod(e EnumType) string that produces Go source code for a String() method. The generated code should follow this pattern:

    func (e TypeName) String() string {
        switch e {
        case Value1:
            return "Value1"
        case Value2:
            return "Value2"
        default:
            return "Unknown"
        }
    }

    Also create a function GenerateConstants(e EnumType) string that produces the constant declarations using iota:

    const (
        Value1 TypeName = iota
        Value2
        Value3
    )

    Finally, create GenerateFullCode(e EnumType) string that combines both outputs with a header comment indicating this is generated code:

    // Code generated by enum generator. DO NOT EDIT.
    
    [constants]
    
    [string method]
  • main.go: Use your generator to produce code for different enum types.

    Read the enum type name, then read a count of values followed by each value name. Pass this information to your generator and print the complete generated code.

The following inputs will be provided:

  • Line 1: Enum type name
  • Line 2: Number of values
  • Following lines: Each value name

For example, given:

Status
3
Pending
Active
Completed

Your output should be:

// Code generated by enum generator. DO NOT EDIT.

const (
    Pending Status = iota
    Active
    Completed
)

func (e Status) String() string {
    switch e {
    case Pending:
        return "Pending"
    case Active:
        return "Active"
    case Completed:
        return "Completed"
    default:
        return "Unknown"
    }
}

And given:

Priority
2
Low
High

Your output should be:

// Code generated by enum generator. DO NOT EDIT.

const (
    Low Priority = iota
    High
)

func (e Priority) String() string {
    switch e {
    case Low:
        return "Low"
    case High:
        return "High"
    default:
        return "Unknown"
    }
}

This exercise demonstrates the core concept behind tools like stringer—generating repetitive but type-safe code at compile time rather than using reflection at runtime. Your generator produces valid Go code that could be saved to a file and compiled with the rest of a project.

Cheat sheet

Code generation creates Go source code at compile time rather than inspecting types at runtime, providing type safety and better performance while reducing boilerplate.

The go generate command automates code generation using special comment directives:

//go:generate stringer -type=Status

type Status int

const (
    Pending Status = iota
    Active
    Completed
)

Running go generate ./... invokes the specified tool (like stringer), which creates a new file containing generated code that gets compiled with your program.

Common code generation tools include:

  • stringer - generates String() methods for enum types
  • mockgen - creates test mocks from interfaces
  • protoc - generates code from Protocol Buffer definitions

Key advantages over reflection:

  • Generated code is checked by the compiler
  • Errors are caught at build time, not runtime
  • Better performance

Trade-offs include an extra build step and generated files to manage in your repository.

Try it yourself

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)

	// Read enum type name
	typeName, _ := reader.ReadString('\n')
	typeName = strings.TrimSpace(typeName)

	// Read number of values
	countStr, _ := reader.ReadString('\n')
	count, _ := strconv.Atoi(strings.TrimSpace(countStr))

	// Read each value name
	values := make([]string, count)
	for i := 0; i < count; i++ {
		value, _ := reader.ReadString('\n')
		values[i] = strings.TrimSpace(value)
	}

	// TODO: Create an EnumType with the read data

	// TODO: Generate and print the full code using GenerateFullCode
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming