Frontend Labs
⋯

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 MeteredConn in 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.


Generated from week1.md · 8 Oct 2026 16:36