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.