Functions in Go: init, Anonymous Functions, Higher-Order Functions, Variadic and defer
Chapter 02 of 03

Functions in Go: init, Anonymous Functions, Higher-Order Functions, Variadic and defer

Every shape a Go function can take, plus callbacks, variadic parameters and the three rules that explain every defer puzzle.

Sabbir Ahmmed4 min read0
On this page

In Go, functions are first-class values: you can store them in variables, pass them to other functions and return them. That one idea unlocks callbacks, middleware, decorators and closures. This article covers every shape a Go function can take, plus two features you will use daily: variadic parameters and defer.

Named functions

The standard form you already know:

go
1
2
3
func add(a, b int) int {
	return a + b
}

Parameters vs arguments: a and b are parameters (the names in the definition). The values you pass when calling, like add(2, 3), are arguments.

The special init function

A package can declare init() functions. Go calls them automatically, once, after package-level variables are initialised and before main runs. You cannot call init yourself, and it takes no parameters and returns nothing.

main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import "fmt"

var num = 10

func init() {
	fmt.Println("init:", num) // init: 10
	num = 100
}

func main() {
	fmt.Println("main:", num) // main: 100
}

Use init sparingly, for things like registering drivers or validating configuration. Hidden work at startup makes programs harder to test and reason about.

Anonymous functions and IIFE

A function without a name is an anonymous function. You can call it immediately, which is called an IIFE (Immediately Invoked Function Expression):

go
1
2
3
func(a, b int) {
	fmt.Println("sum:", a+b)
}(2, 3) // runs right away: sum: 5

This is handy for running a small block with its own scope, and it is the usual way to start a goroutine: go func() { ... }().

Function values (function expressions)

Assign a function to a variable and call it through the variable:

go
1
2
3
4
doSum := func(a, b int) int {
	return a + b
}
fmt.Println(doSum(2, 3)) // 5

Unlike a named function, a function value only exists from the line where it is assigned. Calling doSum before that line is a compile error.

The type of doSum is func(int, int) int. You can name function types to make signatures readable:

go
1
type Operation func(a, b int) int

Higher-order functions and callbacks

A higher-order function takes a function as a parameter, returns one, or both. The function you pass in is often called a callback.

hof.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import "fmt"

type Operation func(a, b int) int

func add(a, b int) int      { return a + b }
func multiply(a, b int) int { return a * b }

// Takes a function as a parameter (callback)
func process(a, b int, op Operation) int {
	return op(a, b)
}

// Returns a function
func pick(name string) Operation {
	if name == "mul" {
		return multiply
	}
	return add
}

func main() {
	fmt.Println(process(2, 3, add))      // 5
	fmt.Println(process(2, 3, multiply)) // 6
	fmt.Println(pick("mul")(4, 5))       // 20
}

This pattern is all over real Go code. For example, sort.Slice takes a "less" function, and HTTP middleware is a function that takes a handler and returns a new one (we build one in the web chapter).

Note
"First-class citizen" just means functions are treated like any other value, the same way 10, true or "hello" are. Higher-order functions come from functional programming (languages like Haskell), which in turn comes from higher-order logic in mathematics.

Variadic functions

A variadic function accepts any number of arguments of one type. Inside the function the parameter is just a slice.

go
1
2
3
4
5
6
7
8
9
10
11
12
13
func sum(nums ...int) int { // nums is []int
	total := 0
	for _, n := range nums {
		total += n
	}
	return total
}

sum()           // 0
sum(1, 2, 3)    // 6

numbers := []int{1, 2, 3, 4, 5}
sum(numbers...) // 15, "spread" an existing slice with ...

The variadic parameter must be the last one. fmt.Println(a ...any) is the most famous example: any (an alias for interface{}) is why it accepts values of different types.

Warning
sum(numbers...) passes the same underlying array, not a copy. If the function modifies nums[0], the caller's slice changes too.

defer: run something when the function returns

defer schedules a call to run when the surrounding function returns, whether it returns normally or because of a panic. It is how Go guarantees cleanup.

go
1
2
3
4
5
f, err := os.Open("data.txt")
if err != nil {
	return err
}
defer f.Close() // always runs, no matter which return path we take

Three rules explain every defer puzzle:

1.Deferred calls run in LIFO order (last deferred, first executed), like a stack.
2.The arguments are evaluated immediately, at the defer line, not when the call finally runs.
3.A deferred closure can read and modify named return values.
defer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import "fmt"

func add(a, b int) {
	fmt.Println("Result:", a+b)
}

func counter() (n int) {
	defer func() { n++ }() // modifies the named result after return 41
	return 41
}

func main() {
	x := 1
	defer fmt.Println("deferred x =", x) // x is captured as 1 right now
	x = 2

	fmt.Println("Start...")
	defer fmt.Println("End")
	defer add(1, 3)
	defer add(2, 3)
	fmt.Println("counter:", counter())
}
Output
1
2
3
4
5
6
Start...
counter: 42
Result: 5
Result: 4
End
deferred x = 1

Read the output from the bottom of the defer stack: add(2, 3) was deferred last, so it runs first. And even though x became 2, the deferred print shows 1 because its argument was evaluated at the defer line.

Tip
Put defer right after acquiring a resource (Open, Lock, Begin). Then the cleanup sits next to the setup and you cannot forget it on an early return. Avoid defer inside long loops: deferred calls only run when the function returns, so they pile up.

Key takeaways

Functions are values: store them, pass them, return them.
init runs automatically before main; keep it small.
Higher-order functions and callbacks power sorting, middleware and decorators.
Variadic parameters are slices; spread a slice with values....
defer is LIFO, evaluates arguments immediately, and can change named results.
#Golang#Functions#defer

Last updated on

Discussion0