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
EasyLet'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
EnumTypestruct that represents an enum-like type definition with aName(string) andValues(slice of strings representing the constant names).Create a function
GenerateStringMethod(e EnumType) stringthat produces Go source code for aString()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) stringthat produces the constant declarations usingiota:const ( Value1 TypeName = iota Value2 Value3 )Finally, create
GenerateFullCode(e EnumType) stringthat 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
CompletedYour 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
HighYour 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- generatesString()methods for enum typesmockgen- creates test mocks from interfacesprotoc- 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
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of Go OOP
External FilesGo Workspace & ModulesPackages & ImportsExported vs Unexported NamesIntroduction to OOP in GoStructs as ClassesDefining Methods on StructsPointer vs Value ReceiversStruct InitializationConstructor FunctionsRecap - Simple Calculator4Interfaces
Introduction to InterfacesImplicit ImplementationInterface as ContractEmpty Interface (any)Type AssertionType SwitchInterface CompositionStringer & Error InterfacesRecap - Shape Calculator7Encapsulation
Exported vs Unexported FieldsPackage-Level EncapsulationGetter & Setter MethodsInformation Hiding in GoRecap - Student Records10Generics (Go 1.18+)
Introduction to GenericsType ParametersType ConstraintsGeneric StructsGeneric Methods WorkaroundRecap - Generic Collection2Types & Structs Deep Dive
Basic & Composite TypesCustom Type DefinitionsStruct TagsAnonymous StructsNested StructsZero Values & DefaultsRecap - Contact Book5Composition Over Inheritance
Why Go Has No InheritanceStruct Embedding BasicsMethod PromotionEmbedding Multiple StructsEmbedding vs AggregationShadowing Embedded MethodsRecap - Employee Hierarchy8Error Handling & OOP
The error InterfaceCustom Error TypesError Wrapping (fmt.Errorf)Sentinel Errorserrors.Is() and errors.As()Panic, Defer, and RecoverRecap - File Parser3Pointers & Memory
Pointer Basics in GoPointers to StructsPass by Value vs ReferenceThe new() FunctionGarbage Collection in GoRecap - Linked List Builder6Polymorphism in Go
Polymorphism via InterfacesDuck Typing in GoInterface Satisfaction RulesPolymorphic CollectionsDependency InjectionRecap - Payment Processor9Concurrency & OOP
Goroutines BasicsChannels & CommunicationBuffered vs Unbuffered ChanSelect Statementsync.Mutex & sync.RWMutexsync.WaitGroupThread-Safe Struct DesignRecap - Worker Pool12Advanced OOP Concepts
Functional Options PatternBuilder Pattern in GoMethod ChainingType Aliases vs DefinitionsReflection BasicsCode Generation Overview