Frontend Labs
⋯

Day 3 — Functions & Multiple Return Values

Learning Objectives

  • Master Go's (value, error) pattern — used in every stdlib call
  • Write composable functions for a config parser
  • Handle errors explicitly at the call site

Key Concepts

// Multiple return values — the core Go pattern
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("division by zero")
    }
    return a / b, nil
}

// Named return values (use sparingly — good for short functions)
func minMax(nums []int) (min, max int) {
    min, max = nums[0], nums[0]
    for _, n := range nums[1:] {
        if n < min { min = n }
        if n > max { max = n }
    }
    return // naked return
}

// Variadic functions
func sum(nums ...int) int {
    total := 0
    for _, n := range nums {
        total += n
    }
    return total
}

// First-class functions — used extensively in middleware
type Handler func(request string) string
type Middleware func(Handler) Handler

Exercise: Config Parser

This is the foundation of the WAF config system you'll build in Week 10.

package main

import (
    "fmt"
    "strconv"
    "strings"
)

type Config struct {
    Host     string
    Port     int
    MaxConns int
    Debug    bool
}

// parseConfig parses "key=value" pairs, one per line
func parseConfig(input string) (Config, error) {
    cfg := Config{
        Host:     "0.0.0.0",
        Port:     8080,
        MaxConns: 1000,
    }

    lines := strings.Split(input, "\n")
    for i, line := range lines {
        line = strings.TrimSpace(line)
        if line == "" || strings.HasPrefix(line, "#") {
            continue
        }

        parts := strings.SplitN(line, "=", 2)
        if len(parts) != 2 {
            return Config{}, fmt.Errorf("line %d: invalid format %q (expected key=value)", i+1, line)
        }

        key := strings.TrimSpace(parts[0])
        val := strings.TrimSpace(parts[1])

        switch key {
        case "host":
            cfg.Host = val
        case "port":
            p, err := strconv.Atoi(val)
            if err != nil {
                return Config{}, fmt.Errorf("line %d: port must be integer, got %q", i+1, val)
            }
            if p < 1 || p > 65535 {
                return Config{}, fmt.Errorf("line %d: port %d out of range [1-65535]", i+1, p)
            }
            cfg.Port = p
        case "max_conns":
            n, err := strconv.Atoi(val)
            if err != nil || n < 1 {
                return Config{}, fmt.Errorf("line %d: max_conns must be positive integer", i+1)
            }
            cfg.MaxConns = n
        case "debug":
            cfg.Debug = val == "true"
        default:
            return Config{}, fmt.Errorf("line %d: unknown key %q", i+1, key)
        }
    }
    return cfg, nil
}

func main() {
    input := `
host=127.0.0.1
port=9090
max_conns=5000
debug=true
`
    cfg, err := parseConfig(input)
    if err != nil {
        fmt.Printf("config error: %v\n", err)
        return
    }
    fmt.Printf("Config: %+v\n", cfg)
}

Real-World Context

Every Go service reads config this way. The (value, error) pattern is how you get compile-time-enforced error handling — unlike exceptions, you can't ignore a returned error silently.


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