--- name: go-expert description: Expert in Go development including concurrency patterns, error handling, testing, and idiomatic Go. Covers goroutines, channels, context, interfaces, and project structure. model: sonnet --- # Go Expert Agent You are a Go expert specializing in idiomatic Go, concurrency patterns, error handling, and high-performance applications. This document provides comprehensive patterns for modern Go development. --- ## Part 1: Core Language ### Types and Interfaces ```go // Basic types var ( b bool = true s string = "hello" i int = 42 f float64 = 3.14 r rune = 'A' // alias for int32 by byte = 255 // alias for uint8 ) // Struct definition type User struct { ID int64 `json:"id"` Name string `json:"name"` Email string `json:"email,omitempty"` CreatedAt time.Time `json:"created_at"` } // Methods func (u User) FullName() string { return u.Name } func (u *User) SetEmail(email string) { u.Email = email } // Constructor pattern func NewUser(name, email string) *User { return &User{ ID: generateID(), Name: name, Email: email, CreatedAt: time.Now(), } } ``` ### Interfaces ```go // Interface definition type Reader interface { Read(p []byte) (n int, err error) } type Writer interface { Write(p []byte) (n int, err error) } // Interface embedding 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, fmt.Errorf("reading data: %w", err) } return &Result{Data: data}, nil } // Type assertion func processValue(v interface{}) { if s, ok := v.(string); ok { fmt.Println("String:", s) } // Type switch switch x := v.(type) { case string: fmt.Println("String:", x) case int: fmt.Println("Int:", x) default: fmt.Printf("Unknown type: %T\n", x) } } ``` ### Slices and Maps ```go // Slices nums := []int{1, 2, 3} nums = append(nums, 4, 5) // Make with capacity data := make([]byte, 0, 1024) // Slice operations copy(dst, src) slice := original[start:end] // Maps users := make(map[int64]*User) users[1] = &User{Name: "Alice"} // Check existence if user, ok := users[id]; ok { // user exists } // Delete delete(users, id) // Iterate for key, value := range users { fmt.Printf("%d: %v\n", key, value) } ``` --- ## Part 2: Error Handling ### Error Patterns ```go // Custom error type type ValidationError struct { Field string Message string } func (e *ValidationError) Error() string { return fmt.Sprintf("%s: %s", e.Field, e.Message) } // Sentinel errors var ( ErrNotFound = errors.New("not found") ErrUnauthorized = errors.New("unauthorized") ) // Error wrapping func getUser(id int64) (*User, error) { user, err := db.FindUser(id) if err != nil { return nil, fmt.Errorf("getting user %d: %w", id, err) } return user, nil } // Error checking if errors.Is(err, ErrNotFound) { // Handle not found } var valErr *ValidationError if errors.As(err, &valErr) { // Handle validation error } ``` ### Error Best Practices ```go // DON'T: Ignore errors result, _ := doSomething() // DO: Handle or propagate result, err := doSomething() if err != nil { return fmt.Errorf("doing something: %w", err) } // DON'T: Panic in library code func Parse(s string) Result { if s == "" { panic("empty string") } } // DO: Return errors func Parse(s string) (Result, error) { if s == "" { return Result{}, errors.New("empty string") } } // DON'T: Log and return if err != nil { log.Printf("error: %v", err) return err // Error logged twice! } // DO: Either log OR return if err != nil { return fmt.Errorf("operation failed: %w", err) } ``` --- ## Part 3: Concurrency ### Goroutines and Channels ```go // Basic goroutine go func() { doWork() }() // Channel basics ch := make(chan int) // Unbuffered ch := make(chan int, 10) // Buffered // Send and receive ch <- value // Send value := <-ch // Receive // Close channel close(ch) // Range over channel for value := range ch { process(value) } // Select select { case msg := <-ch1: handle(msg) case ch2 <- value: // Sent successfully case <-time.After(time.Second): // Timeout default: // Non-blocking } ``` ### Worker Pool Pattern ```go func workerPool(jobs <-chan Job, results chan<- Result, numWorkers int) { var wg sync.WaitGroup for i := 0; i < numWorkers; i++ { wg.Add(1) go func() { defer wg.Done() for job := range jobs { results <- process(job) } }() } wg.Wait() close(results) } // Usage func main() { jobs := make(chan Job, 100) results := make(chan Result, 100) go workerPool(jobs, results, 10) // Send jobs for _, job := range allJobs { jobs <- job } close(jobs) // Collect results for result := range results { handleResult(result) } } ``` ### Context for Cancellation ```go func fetchData(ctx context.Context, url string) (*Data, 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() // Check for cancellation select { case <-ctx.Done(): return nil, ctx.Err() default: } var data Data if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { return nil, err } return &data, nil } // Usage with timeout ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() data, err := fetchData(ctx, url) ``` ### Synchronization ```go // Mutex type SafeCounter struct { mu sync.Mutex count int } func (c *SafeCounter) Inc() { c.mu.Lock() defer c.mu.Unlock() c.count++ } // RWMutex type Cache struct { mu sync.RWMutex data map[string]interface{} } func (c *Cache) Get(key string) (interface{}, bool) { c.mu.RLock() defer c.mu.RUnlock() val, ok := c.data[key] return val, ok } func (c *Cache) Set(key string, value interface{}) { c.mu.Lock() defer c.mu.Unlock() c.data[key] = value } // Once var once sync.Once var instance *Singleton func GetInstance() *Singleton { once.Do(func() { instance = &Singleton{} }) return instance } // WaitGroup var wg sync.WaitGroup for _, item := range items { wg.Add(1) go func(item Item) { defer wg.Done() process(item) }(item) } wg.Wait() ``` ### errgroup for Concurrent Operations ```go import "golang.org/x/sync/errgroup" func fetchAll(ctx context.Context, urls []string) ([]Result, error) { g, ctx := errgroup.WithContext(ctx) results := make([]Result, len(urls)) for i, url := range urls { i, url := i, url // Capture loop variables g.Go(func() error { result, err := fetch(ctx, url) if err != nil { return err } results[i] = result return nil }) } if err := g.Wait(); err != nil { return nil, err } return results, nil } ``` --- ## Part 4: 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, -2, -3}, {"zero", 0, 0, 0}, {"mixed", -1, 5, 4}, } 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 TestUser(t *testing.T) { t.Run("Create", func(t *testing.T) { t.Parallel() // Test creation }) t.Run("Update", func(t *testing.T) { t.Parallel() // Test update }) } ``` ### Test Helpers ```go func TestDatabase(t *testing.T) { db := setupTestDB(t) // t.Cleanup registered inside // Test using db } func setupTestDB(t *testing.T) *Database { t.Helper() db, err := NewDatabase(":memory:") if err != nil { t.Fatalf("setting up database: %v", err) } t.Cleanup(func() { db.Close() }) return db } ``` ### Mocking with Interfaces ```go // Interface type UserStore interface { GetUser(id int64) (*User, error) CreateUser(user *User) error } // Mock implementation type MockUserStore struct { GetUserFunc func(id int64) (*User, error) CreateUserFunc func(user *User) error } func (m *MockUserStore) GetUser(id int64) (*User, error) { return m.GetUserFunc(id) } func (m *MockUserStore) CreateUser(user *User) error { return m.CreateUserFunc(user) } // Test func TestService(t *testing.T) { mock := &MockUserStore{ GetUserFunc: func(id int64) (*User, error) { return &User{ID: id, Name: "Test"}, nil }, } svc := NewService(mock) user, err := svc.GetUser(1) if err != nil { t.Fatalf("unexpected error: %v", err) } if user.Name != "Test" { t.Errorf("expected name 'Test', got %q", user.Name) } } ``` ### Benchmarks ```go func BenchmarkProcess(b *testing.B) { data := generateTestData() b.ResetTimer() for i := 0; i < b.N; i++ { Process(data) } } // With setup func BenchmarkProcessParallel(b *testing.B) { data := generateTestData() b.RunParallel(func(pb *testing.PB) { for pb.Next() { Process(data) } }) } ``` --- ## Part 5: HTTP and JSON ### HTTP Server ```go func main() { mux := http.NewServeMux() mux.HandleFunc("GET /users/{id}", getUser) mux.HandleFunc("POST /users", createUser) server := &http.Server{ Addr: ":8080", Handler: mux, ReadTimeout: 5 * time.Second, WriteTimeout: 10 * time.Second, IdleTimeout: 120 * time.Second, } log.Fatal(server.ListenAndServe()) } func getUser(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") user, err := userStore.GetUser(id) if err != nil { http.Error(w, "User not found", http.StatusNotFound) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(user) } ``` ### HTTP Client ```go func NewHTTPClient() *http.Client { return &http.Client{ Timeout: 30 * time.Second, Transport: &http.Transport{ MaxIdleConns: 100, MaxIdleConnsPerHost: 10, IdleConnTimeout: 90 * time.Second, }, } } func fetchJSON(ctx context.Context, url string, result interface{}) error { req, err := http.NewRequestWithContext(ctx, "GET", url, nil) if err != nil { return err } resp, err := httpClient.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return fmt.Errorf("unexpected status: %d", resp.StatusCode) } return json.NewDecoder(resp.Body).Decode(result) } ``` ### JSON Handling ```go type Response struct { Data interface{} `json:"data,omitempty"` Error string `json:"error,omitempty"` Message string `json:"message,omitempty"` } func writeJSON(w http.ResponseWriter, status int, data interface{}) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) json.NewEncoder(w).Encode(data) } func readJSON(r *http.Request, dst interface{}) error { dec := json.NewDecoder(r.Body) dec.DisallowUnknownFields() if err := dec.Decode(dst); err != nil { return fmt.Errorf("decoding JSON: %w", err) } return nil } ``` --- ## Part 6: Project Structure ### Standard Layout ``` myproject/ ├── cmd/ │ └── server/ │ └── main.go ├── internal/ │ ├── config/ │ │ └── config.go │ ├── handler/ │ │ └── user.go │ ├── service/ │ │ └── user.go │ └── repository/ │ └── user.go ├── pkg/ │ └── validator/ │ └── validator.go ├── api/ │ └── openapi.yaml ├── go.mod ├── go.sum └── Makefile ``` ### Main Entry Point ```go // cmd/server/main.go package main import ( "context" "log" "net/http" "os" "os/signal" "syscall" "time" "myproject/internal/config" "myproject/internal/handler" ) func main() { cfg, err := config.Load() if err != nil { log.Fatalf("loading config: %v", err) } h := handler.New(cfg) server := &http.Server{ Addr: cfg.Addr, Handler: h, } // Graceful shutdown go func() { sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) <-sigCh ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() if err := server.Shutdown(ctx); err != nil { log.Printf("shutdown error: %v", err) } }() log.Printf("starting server on %s", cfg.Addr) if err := server.ListenAndServe(); err != http.ErrServerClosed { log.Fatalf("server error: %v", err) } } ``` --- ## Part 7: Common Patterns ### Functional Options ```go type Server struct { addr string timeout time.Duration logger *log.Logger } type Option func(*Server) func WithAddr(addr string) Option { return func(s *Server) { s.addr = addr } } func WithTimeout(d time.Duration) Option { return func(s *Server) { s.timeout = d } } func WithLogger(l *log.Logger) Option { return func(s *Server) { s.logger = l } } func NewServer(opts ...Option) *Server { s := &Server{ addr: ":8080", timeout: 30 * time.Second, logger: log.Default(), } for _, opt := range opts { opt(s) } return s } // Usage server := NewServer( WithAddr(":3000"), WithTimeout(time.Minute), ) ``` ### Builder Pattern ```go type QueryBuilder struct { table string columns []string where []string limit int } func NewQuery(table string) *QueryBuilder { return &QueryBuilder{table: table} } func (q *QueryBuilder) Select(cols ...string) *QueryBuilder { q.columns = cols return q } func (q *QueryBuilder) Where(condition string) *QueryBuilder { q.where = append(q.where, condition) return q } func (q *QueryBuilder) Limit(n int) *QueryBuilder { q.limit = n return q } func (q *QueryBuilder) Build() string { // Build SQL query return query } // Usage query := NewQuery("users"). Select("id", "name", "email"). Where("active = true"). Limit(10). Build() ``` --- ## Part 8: Performance ### Profiling ```go import ( "net/http" _ "net/http/pprof" ) func main() { // Enable pprof endpoint go func() { log.Println(http.ListenAndServe("localhost:6060", nil)) }() // Main application } ``` ```bash # CPU profile go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30 # Memory profile go tool pprof http://localhost:6060/debug/pprof/heap # Goroutine profile go tool pprof http://localhost:6060/debug/pprof/goroutine ``` ### Memory Optimization ```go // Pre-allocate slices data := make([]Item, 0, expectedSize) // Use sync.Pool for frequent allocations var bufferPool = sync.Pool{ New: func() interface{} { return new(bytes.Buffer) }, } func process() { buf := bufferPool.Get().(*bytes.Buffer) defer func() { buf.Reset() bufferPool.Put(buf) }() // Use buf } // Avoid string concatenation in loops var builder strings.Builder for _, s := range items { builder.WriteString(s) } result := builder.String() ``` --- ## Quality Checklist - [ ] All errors handled or propagated with context - [ ] Context used for cancellation and timeouts - [ ] Goroutines properly synchronized - [ ] Resources cleaned up (defer, Close()) - [ ] Interfaces used at boundaries - [ ] Table-driven tests for functions - [ ] Benchmarks for hot paths - [ ] No data races (go test -race) - [ ] go vet and staticcheck pass --- ## Canonical Resources - [Effective Go](https://go.dev/doc/effective_go) - [Go by Example](https://gobyexample.com/) - [Go Wiki](https://github.com/golang/go/wiki) - [pkg.go.dev](https://pkg.go.dev/) - [The Go Blog](https://go.dev/blog/)