--- name: go-patterns description: Modern Go patterns, idioms, and best practices from Go 1.18+. Use when user needs guidance on idiomatic Go code, design patterns, or modern Go features like generics and workspaces. --- # Go Patterns Skill This skill provides comprehensive guidance on modern Go patterns, idioms, and best practices, with special focus on features introduced in Go 1.18 and later. ## When to Use Activate this skill when: - Writing idiomatic Go code - Implementing design patterns in Go - Using modern Go features (generics, fuzzing, workspaces) - Refactoring code to be more idiomatic - Teaching Go best practices - Code review for idiom compliance ## Modern Go Features ### Generics (Go 1.18+) **Type Parameters:** ```go // Generic function func Map[T, U any](slice []T, f func(T) U) []U { result := make([]U, len(slice)) for i, v := range slice { result[i] = f(v) } return result } // Usage numbers := []int{1, 2, 3, 4, 5} doubled := Map(numbers, func(n int) int { return n * 2 }) ``` **Type Constraints:** ```go // Ordered constraint type Ordered interface { ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~float32 | ~float64 | ~string } func Min[T Ordered](a, b T) T { if a < b { return a } return b } // Custom constraints type Numeric interface { ~int | ~int64 | ~float64 } func Sum[T Numeric](values []T) T { var sum T for _, v := range values { sum += v } return sum } ``` **Generic Data Structures:** ```go // Generic stack type Stack[T any] struct { items []T } func NewStack[T any]() *Stack[T] { return &Stack[T]{items: make([]T, 0)} } func (s *Stack[T]) Push(item T) { s.items = append(s.items, item) } func (s *Stack[T]) Pop() (T, bool) { if len(s.items) == 0 { var zero T return zero, false } item := s.items[len(s.items)-1] s.items = s.items[:len(s.items)-1] return item, true } // Generic map utilities func Keys[K comparable, V any](m map[K]V) []K { keys := make([]K, 0, len(m)) for k := range m { keys = append(keys, k) } return keys } func Values[K comparable, V any](m map[K]V) []V { values := make([]V, 0, len(m)) for _, v := range m { values = append(values, v) } return values } ``` ### Workspaces (Go 1.18+) **go.work file:** ``` go 1.21 use ( ./service ./shared ./tools ) replace example.com/legacy => ./vendor/legacy ``` **Benefits:** - Multi-module development - Local dependency overrides - Simplified testing across modules - Better monorepo support ## Essential Go Patterns ### Functional Options Pattern ```go type Server struct { host string port int timeout time.Duration logger *log.Logger } type Option func(*Server) func WithHost(host string) Option { return func(s *Server) { s.host = host } } func WithPort(port int) Option { return func(s *Server) { s.port = port } } func WithTimeout(timeout time.Duration) Option { return func(s *Server) { s.timeout = timeout } } func WithLogger(logger *log.Logger) Option { return func(s *Server) { s.logger = logger } } func NewServer(opts ...Option) *Server { s := &Server{ host: "localhost", port: 8080, timeout: 30 * time.Second, logger: log.Default(), } for _, opt := range opts { opt(s) } return s } // Usage server := NewServer( WithHost("0.0.0.0"), WithPort(3000), WithTimeout(60 * time.Second), ) ``` ### Builder Pattern ```go type Query struct { table string where []string orderBy string limit int offset int } type QueryBuilder struct { query Query } func NewQueryBuilder(table string) *QueryBuilder { return &QueryBuilder{ query: Query{table: table}, } } func (b *QueryBuilder) Where(condition string) *QueryBuilder { b.query.where = append(b.query.where, condition) return b } func (b *QueryBuilder) OrderBy(field string) *QueryBuilder { b.query.orderBy = field return b } func (b *QueryBuilder) Limit(limit int) *QueryBuilder { b.query.limit = limit return b } func (b *QueryBuilder) Offset(offset int) *QueryBuilder { b.query.offset = offset return b } func (b *QueryBuilder) Build() Query { return b.query } // Usage query := NewQueryBuilder("users"). Where("age > 18"). Where("active = true"). OrderBy("created_at DESC"). Limit(10). Offset(20). Build() ``` ### Strategy Pattern ```go // Strategy interface type PaymentStrategy interface { Pay(amount float64) error } // Concrete strategies type CreditCardPayment struct { cardNumber string } func (c *CreditCardPayment) Pay(amount float64) error { fmt.Printf("Paying $%.2f with credit card %s\n", amount, c.cardNumber) return nil } type PayPalPayment struct { email string } func (p *PayPalPayment) Pay(amount float64) error { fmt.Printf("Paying $%.2f with PayPal account %s\n", amount, p.email) return nil } type CryptoPayment struct { walletAddress string } func (c *CryptoPayment) Pay(amount float64) error { fmt.Printf("Paying $%.2f to wallet %s\n", amount, c.walletAddress) return nil } // Context type PaymentProcessor struct { strategy PaymentStrategy } func NewPaymentProcessor(strategy PaymentStrategy) *PaymentProcessor { return &PaymentProcessor{strategy: strategy} } func (p *PaymentProcessor) ProcessPayment(amount float64) error { return p.strategy.Pay(amount) } // Usage processor := NewPaymentProcessor(&CreditCardPayment{cardNumber: "1234-5678"}) processor.ProcessPayment(100.00) processor = NewPaymentProcessor(&PayPalPayment{email: "user@example.com"}) processor.ProcessPayment(50.00) ``` ### Observer Pattern ```go type Observer interface { Update(event Event) } type Event struct { Type string Data interface{} } type Subject struct { observers []Observer } func (s *Subject) Attach(observer Observer) { s.observers = append(s.observers, observer) } func (s *Subject) Detach(observer Observer) { for i, obs := range s.observers { if obs == observer { s.observers = append(s.observers[:i], s.observers[i+1:]...) break } } } func (s *Subject) Notify(event Event) { for _, observer := range s.observers { observer.Update(event) } } // Concrete observer type Logger struct { name string } func (l *Logger) Update(event Event) { fmt.Printf("[%s] Received event: %s\n", l.name, event.Type) } // Usage subject := &Subject{} logger1 := &Logger{name: "Logger1"} logger2 := &Logger{name: "Logger2"} subject.Attach(logger1) subject.Attach(logger2) subject.Notify(Event{Type: "UserCreated", Data: "user123"}) ``` ## Idiomatic Go Patterns ### Error Handling **Sentinel Errors:** ```go var ( ErrNotFound = errors.New("resource not found") ErrUnauthorized = errors.New("unauthorized access") ErrInvalidInput = errors.New("invalid input") ) func GetUser(id string) (*User, error) { if id == "" { return nil, ErrInvalidInput } user := findUser(id) if user == nil { return nil, ErrNotFound } return user, nil } // Check with errors.Is if errors.Is(err, ErrNotFound) { // Handle not found } ``` **Custom Error Types:** ```go type ValidationError struct { Field string Message string } func (e *ValidationError) Error() string { return fmt.Sprintf("validation error on %s: %s", e.Field, e.Message) } // Check with errors.As var valErr *ValidationError if errors.As(err, &valErr) { fmt.Printf("Validation failed: %s\n", valErr.Field) } ``` **Error Wrapping:** ```go func ProcessUser(id string) error { user, err := GetUser(id) if err != nil { return fmt.Errorf("process user: %w", err) } if err := ValidateUser(user); err != nil { return fmt.Errorf("validate user %s: %w", id, err) } return nil } ``` ### Interface Patterns **Small Interfaces:** ```go // Good: Small, focused interfaces type Reader interface { Read(p []byte) (n int, err error) } type Writer interface { Write(p []byte) (n int, err error) } type Closer interface { Close() error } // Compose interfaces type ReadWriteCloser interface { Reader Writer Closer } ``` **Interface Segregation:** ```go // Instead of one large interface type Repository interface { Create(ctx context.Context, user *User) error Read(ctx context.Context, id string) (*User, error) Update(ctx context.Context, user *User) error Delete(ctx context.Context, id string) error List(ctx context.Context) ([]*User, error) Search(ctx context.Context, query string) ([]*User, error) } // Better: Separate interfaces type UserCreator interface { Create(ctx context.Context, user *User) error } type UserReader interface { Read(ctx context.Context, id string) (*User, error) List(ctx context.Context) ([]*User, error) } type UserUpdater interface { Update(ctx context.Context, user *User) error } type UserDeleter interface { Delete(ctx context.Context, id string) error } type UserSearcher interface { Search(ctx context.Context, query string) ([]*User, error) } ``` ### Context Patterns **Proper Context Usage:** ```go func FetchData(ctx context.Context, url string) ([]byte, error) { // Create request with context req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return nil, fmt.Errorf("create request: %w", err) } // Check for cancellation before expensive operation select { case <-ctx.Done(): return nil, ctx.Err() default: } // Execute request resp, err := http.DefaultClient.Do(req) if err != nil { return nil, fmt.Errorf("execute request: %w", err) } defer resp.Body.Close() return io.ReadAll(resp.Body) } // Context with timeout ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() data, err := FetchData(ctx, "https://api.example.com/data") ``` **Context Values:** ```go type contextKey string const ( requestIDKey contextKey = "requestID" userIDKey contextKey = "userID" ) func WithRequestID(ctx context.Context, requestID string) context.Context { return context.WithValue(ctx, requestIDKey, requestID) } func GetRequestID(ctx context.Context) (string, bool) { requestID, ok := ctx.Value(requestIDKey).(string) return requestID, ok } func WithUserID(ctx context.Context, userID string) context.Context { return context.WithValue(ctx, userIDKey, userID) } func GetUserID(ctx context.Context) (string, bool) { userID, ok := ctx.Value(userIDKey).(string) return userID, ok } ``` ## Best Practices 1. **Accept interfaces, return structs** 2. **Make the zero value useful** 3. **Use composition over inheritance** 4. **Handle errors explicitly** 5. **Use defer for cleanup** 6. **Prefer sync.RWMutex for read-heavy workloads** 7. **Use context for cancellation and timeouts** 8. **Keep interfaces small** 9. **Document exported identifiers** 10. **Use go fmt and go vet** ## Resources Additional patterns and examples are available in the `assets/` directory: - `examples/` - Complete code examples - `patterns/` - Design pattern implementations - `antipatterns/` - Common mistakes to avoid See `references/` directory for: - Links to official Go documentation - Effective Go guidelines - Go proverbs - Community best practices