Day 5 — Structs & Methods
Learning Objectives
- Model real-world entities with structs
- Choose between value and pointer receivers correctly
- Use struct embedding for transparent wrappers (the pattern used in
MeteredConnin Week 7)
Key Concepts
// Struct with field tags (used by encoding/json, database drivers)
type ServerConfig struct {
Host string `json:"host"`
Port int `json:"port"`
Timeout time.Duration `json:"timeout_ms"` // custom unmarshal needed
TLSCert string `json:"tls_cert,omitempty"` // omit if empty
}
// Value receiver — method gets a copy, cannot modify
func (c ServerConfig) Address() string {
return fmt.Sprintf("%s:%d", c.Host, c.Port)
}
// Pointer receiver — method can modify, avoids copy for large structs
func (c *ServerConfig) SetDefaults() {
if c.Host == "" { c.Host = "0.0.0.0" }
if c.Port == 0 { c.Port = 8080 }
if c.Timeout == 0 { c.Timeout = 30 * time.Second }
}
// Rule: if ANY method uses a pointer receiver, use pointer receivers for ALL methods on that type
Exercise: Server Struct
package main
import (
"fmt"
"sync/atomic"
"time"
)
type Logger struct {
prefix string
}
func (l *Logger) Log(msg string) {
fmt.Printf("[%s] %s %s\n", time.Now().Format("15:04:05"), l.prefix, msg)
}
type Server struct {
Logger // embedded — Server.Log() works directly
host string
port int
startedAt time.Time
running atomic.Bool
}
func NewServer(host string, port int) *Server {
return &Server{
Logger: Logger{prefix: "SERVER"},
host: host,
port: port,
}
}
func (s *Server) Start() error {
if s.running.Swap(true) {
return fmt.Errorf("server already running")
}
s.startedAt = time.Now()
s.Log(fmt.Sprintf("starting on %s:%d", s.host, s.port))
return nil
}
func (s *Server) Stop() {
if !s.running.Swap(false) {
return
}
s.Log(fmt.Sprintf("stopped after %v", time.Since(s.startedAt)))
}
func (s *Server) Status() string {
if s.running.Load() {
return fmt.Sprintf("running for %v on %s:%d", time.Since(s.startedAt), s.host, s.port)
}
return "stopped"
}
func main() {
srv := NewServer("0.0.0.0", 8080)
srv.Start()
fmt.Println(srv.Status())
time.Sleep(100 * time.Millisecond)
srv.Stop()
fmt.Println(srv.Status())
}
Real-World Context
The embedded Logger pattern is how Go services compose behavior without inheritance. Your WAF middleware, rate limiter, and proxy will all use this pattern. atomic.Bool for running prevents races — you'll need this when tests run Start() and Stop() concurrently.