Frontend Labs
⋯

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.


Generated from real-life-golang-guide.md · 8 Oct 2026 16:36