---
description: "Go development: error handling, interfaces, concurrency, modules, and idiomatic patterns"
globs: ["**/*.go", "**/go.mod", "**/go.sum"]
alwaysApply: false
---
# Go Development Patterns
Idiomatic Go patterns focusing on simplicity, clarity, and concurrency.
## CRITICAL: Agentic-First Go Development
### Pre-Development Verification (MANDATORY)
Before writing ANY Go code:
```
1. CHECK GO INSTALLATION
→ run_terminal_cmd("go version")
2. VERIFY CURRENT GO VERSION (use web_search)
→ web_search("Go golang stable version December 2024")
→ Ensure go.mod uses correct version
3. CHECK EXISTING PROJECT
→ Does go.mod exist? Read it first!
→ What Go version is specified?
→ Are dependencies already present?
4. FOR NEW PROJECTS - USE go mod init
→ NEVER manually create go.mod
→ run_terminal_cmd("go mod init github.com/org/project")
```
### CLI-First Go Development
**ALWAYS use Go CLI tools:**
```bash
# Project initialization (NEVER manually create go.mod)
go mod init github.com/myorg/myproject
# Add dependencies (NEVER manually edit go.mod)
go get github.com/gin-gonic/gin@latest
go get github.com/lib/pq@v1.10.9
# Verify and tidy
go mod tidy
go mod verify
# Build and test
go build ./...
go test ./...
go vet ./...
# Format (ALWAYS before committing)
go fmt ./...
gofmt -s -w .
```
### Post-Edit Verification
After ANY Go code changes, ALWAYS run:
```bash
# Verify compilation
go build ./...
# Check for issues
go vet ./...
# Run tests
go test ./...
# Ensure dependencies are correct
go mod tidy
```
### Common Go Syntax Traps (Avoid These!)
```go
// WRONG: Missing error check
result, _ := doSomething() // Never ignore errors!
// CORRECT: Always handle errors
result, err := doSomething()
if err != nil {
return fmt.Errorf("doSomething failed: %w", err)
}
// WRONG: Range loop variable capture
for _, item := range items {
go func() {
process(item) // Bug! Captures loop variable
}()
}
// CORRECT: Pass as parameter
for _, item := range items {
go func(i Item) {
process(i)
}(item)
}
// WRONG: Nil map write
var m map[string]int
m["key"] = 1 // Panic!
// CORRECT: Initialize map
m := make(map[string]int)
m["key"] = 1
```
---
## Error Handling
### Basic Error Handling
```go
// Always check errors
result, err := doSomething()
if err != nil {
return fmt.Errorf("doSomething failed: %w", err)
}
// Wrap errors with context
if err := db.Query(sql); err != nil {
return fmt.Errorf("failed to query users: %w", err)
}
```
### Custom Error Types
```go
// Simple error type
type NotFoundError struct {
Resource string
ID string
}
func (e *NotFoundError) Error() string {
return fmt.Sprintf("%s with id %s not found", e.Resource, e.ID)
}
// Checking error types
if errors.Is(err, sql.ErrNoRows) {
return &NotFoundError{Resource: "user", ID: id}
}
// Type assertions
var notFound *NotFoundError
if errors.As(err, ¬Found) {
// Handle not found case
}
```
### Error Handling Patterns
```go
// Early return pattern
func processData(data []byte) error {
if len(data) == 0 {
return errors.New("empty data")
}
parsed, err := parse(data)
if err != nil {
return fmt.Errorf("parse failed: %w", err)
}
if err := validate(parsed); err != nil {
return fmt.Errorf("validation failed: %w", err)
}
return save(parsed)
}
// Error sentinel values
var (
ErrNotFound = errors.New("not found")
ErrUnauthorized = errors.New("unauthorized")
ErrInvalidInput = errors.New("invalid input")
)
```
---
## Interfaces
### Interface Design
```go
// Small, focused interfaces
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
type ReadWriter interface {
Reader
Writer
}
// Accept interfaces, return structs
func ProcessData(r io.Reader) (*Result, error) {
data, err := io.ReadAll(r)
if err != nil {
return nil, err
}
return &Result{Data: data}, nil
}
```
### Interface Best Practices
```go
// Define interfaces where they're used, not where implemented
// In consumer package:
type UserStore interface {
Get(id string) (*User, error)
Save(user *User) error
}
type UserService struct {
store UserStore // Accepts any implementation
}
// Implementation in another package
type PostgresUserStore struct {
db *sql.DB
}
func (s *PostgresUserStore) Get(id string) (*User, error) { ... }
func (s *PostgresUserStore) Save(user *User) error { ... }
```
### Empty Interface and Type Assertions
```go
// Avoid interface{} / any when possible
// When necessary, use type switches
func process(v any) string {
switch x := v.(type) {
case string:
return x
case int:
return strconv.Itoa(x)
case fmt.Stringer:
return x.String()
default:
return fmt.Sprintf("%v", v)
}
}
```
---
## Concurrency
### Goroutines and Channels
```go
// Basic goroutine
go func() {
result := expensiveOperation()
resultChan <- result
}()
// Buffered channel
jobs := make(chan Job, 100)
// Wait for completion
var wg sync.WaitGroup
for i := 0; i < numWorkers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for job := range jobs {
process(job)
}
}()
}
wg.Wait()
```
### Context for Cancellation
```go
func fetchData(ctx context.Context, url string) ([]byte, error) {
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
return io.ReadAll(resp.Body)
}
// Usage with timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
data, err := fetchData(ctx, "https://api.example.com")
```
### Worker Pool Pattern
```go
func WorkerPool(ctx context.Context, jobs <-chan Job, numWorkers int) <-chan Result {
results := make(chan Result)
var wg sync.WaitGroup
for i := 0; i < numWorkers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for {
select {
case job, ok := <-jobs:
if !ok {
return
}
results <- process(job)
case <-ctx.Done():
return
}
}
}()
}
go func() {
wg.Wait()
close(results)
}()
return results
}
```
### Mutex and Atomic Operations
```go
type Counter struct {
mu sync.Mutex
value int
}
func (c *Counter) Increment() {
c.mu.Lock()
defer c.mu.Unlock()
c.value++
}
func (c *Counter) Value() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.value
}
// For simple counters, use atomic
type AtomicCounter struct {
value atomic.Int64
}
func (c *AtomicCounter) Increment() {
c.value.Add(1)
}
```
---
## Structs and Methods
### Struct Design
```go
type User struct {
ID string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
CreatedAt time.Time `json:"created_at"`
}
// Constructor function
func NewUser(name, email string) *User {
return &User{
ID: uuid.New().String(),
Name: name,
Email: email,
CreatedAt: time.Now(),
}
}
```
### Method Receivers
```go
// Value receiver - doesn't modify, safe for concurrent use
func (u User) FullName() string {
return u.FirstName + " " + u.LastName
}
// Pointer receiver - can modify, more efficient for large structs
func (u *User) UpdateEmail(email string) error {
if !isValidEmail(email) {
return errors.New("invalid email")
}
u.Email = email
return nil
}
```
### Embedded Structs
```go
type Timestamps struct {
CreatedAt time.Time
UpdatedAt time.Time
}
type User struct {
Timestamps // Embedded - fields promoted
ID string
Name string
}
// Access promoted fields directly
user.CreatedAt = time.Now()
```
---
## Testing
### Table-Driven Tests
```go
func TestAdd(t *testing.T) {
tests := []struct {
name string
a, b int
expected int
}{
{"positive", 2, 3, 5},
{"negative", -1, -1, -2},
{"zero", 0, 0, 0},
{"mixed", -1, 1, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := Add(tt.a, tt.b)
if result != tt.expected {
t.Errorf("Add(%d, %d) = %d; want %d",
tt.a, tt.b, result, tt.expected)
}
})
}
}
```
### Subtests and Parallel
```go
func TestUserService(t *testing.T) {
t.Run("Create", func(t *testing.T) {
t.Parallel()
// test create
})
t.Run("Get", func(t *testing.T) {
t.Parallel()
// test get
})
}
```
### Mocking with Interfaces
```go
// Interface in production code
type UserRepository interface {
Get(id string) (*User, error)
Save(user *User) error
}
// Mock in test
type MockUserRepository struct {
users map[string]*User
}
func (m *MockUserRepository) Get(id string) (*User, error) {
user, ok := m.users[id]
if !ok {
return nil, ErrNotFound
}
return user, nil
}
func TestUserService_GetUser(t *testing.T) {
repo := &MockUserRepository{
users: map[string]*User{
"1": {ID: "1", Name: "Test"},
},
}
service := NewUserService(repo)
user, err := service.Get("1")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if user.Name != "Test" {
t.Errorf("expected name 'Test', got '%s'", user.Name)
}
}
```
---
## Project Structure
### Standard Layout
```
myproject/
cmd/
myapp/
main.go # Application entry point
internal/ # Private application code
config/
handler/
service/
repository/
pkg/ # Public library code
utils/
api/ # API definitions (OpenAPI, protobuf)
web/ # Web assets
scripts/ # Build/CI scripts
go.mod
go.sum
```
### Package Organization
```go
// cmd/myapp/main.go
package main
import (
"myproject/internal/config"
"myproject/internal/handler"
"myproject/internal/service"
)
func main() {
cfg := config.Load()
svc := service.New(cfg)
h := handler.New(svc)
http.ListenAndServe(":8080", h)
}
```
---
## HTTP Handlers
### Handler Functions
```go
func handleGetUser(svc *UserService) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
user, err := svc.Get(r.Context(), id)
if err != nil {
if errors.Is(err, ErrNotFound) {
http.Error(w, "user not found", http.StatusNotFound)
return
}
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(user)
}
}
```
### Middleware
```go
func LoggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// Wrap response writer to capture status
wrapped := &responseWriter{ResponseWriter: w, status: http.StatusOK}
next.ServeHTTP(wrapped, r)
log.Printf("%s %s %d %v",
r.Method, r.URL.Path, wrapped.status, time.Since(start))
})
}
type responseWriter struct {
http.ResponseWriter
status int
}
func (w *responseWriter) WriteHeader(status int) {
w.status = status
w.ResponseWriter.WriteHeader(status)
}
```
---
## Go Modules
### go.mod
```go
module github.com/myorg/myproject
go 1.21
require (
github.com/go-chi/chi/v5 v5.0.10
github.com/lib/pq v1.10.9
)
require (
// indirect dependencies
)
```
### Common Commands
```bash
# Initialize module
go mod init github.com/myorg/myproject
# Add dependencies
go get github.com/go-chi/chi/v5
# Tidy dependencies
go mod tidy
# Update dependencies
go get -u ./...
# Vendor dependencies
go mod vendor
```
---
## Code Style
### Naming Conventions
```go
// Exported (public) - PascalCase
type UserService struct { ... }
func NewUserService() *UserService { ... }
// Unexported (private) - camelCase
type userCache struct { ... }
func validateEmail(email string) bool { ... }
// Acronyms - all caps
var httpClient *http.Client
type JSONResponse struct { ... }
userID := "123" // not userId
```
### Comments
```go
// Package users provides user management functionality.
package users
// User represents a registered user in the system.
type User struct {
// ID is the unique identifier for the user.
ID string
// Name is the user's display name.
Name string
}
// NewUser creates a new user with the given name.
// It returns an error if the name is empty.
func NewUser(name string) (*User, error) {
...
}
```
### Formatting
```bash
# Always format code
go fmt ./...
# Lint code
golangci-lint run
```