5. Functions: multiple returns, variadics, closures
5.1 Multiple return values and the (value, error) idiom
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
This single pattern — return the real result and an explicit error —
is why Go code reads the same everywhere: every call site immediately
answers "did this work?" without a hidden control-flow jump. There's no
exception to catch three call frames up; the caller either handles the
error right there or explicitly propagates it.
5.2 Named returns
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return // "naked" return sends back the current x, y
}
Named returns are most useful combined with defer to modify a return
value after the fact — e.g. wrapping an error with context right before
the function actually returns:
func doWork() (err error) {
defer func() {
if err != nil {
err = fmt.Errorf("doWork: %w", err)
}
}()
return someOperation()
}
5.3 Variadic functions
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
sum(1, 2, 3) // nums = []int{1,2,3}
sum(existingSlice...) // spread a slice into variadic args
5.4 Closures
Functions are values; they close over variables by reference, not by value:
func counter() func() int {
n := 0
return func() int {
n++
return n
}
}
next := counter()
next() // 1
next() // 2
5.5 Real-world example
A rate limiter is naturally a closure: NewLimiter(rps int) func() bool
returns a function that closes over a token bucket and internal timers,
so callers never see (or can misuse) the bucket's internal state — only
the decision. This is the functional-options-adjacent pattern you'll use
constantly for middleware (Chapter 24) and for the WAF-style rate limiter
in Chapter 49.