Functions
Overview
-
Declaring a function:
func add(a int, b int) int { return a + b } -
Calling a function:
result := add(2, 3) // result = 5
Extra:
-
Functions can have multiple return values:
func swap(a, b int) (int, int) { return b, a } x, y := 1, 2 x, y = swap(x, y) // x = 2, y = 1 -
Ignoring return values:
x, _ = getCoords() -
Named return values:
With named return values, you can use return without specifying values. The function automatically returns the named variables.
func calculate(x int) (result int) { result = x * x return } -
Variadic functions:
Variadic functions accept zero or more arguments of the same type, which are treated as a slice inside the function.
func sum(numbers ...int) int { total := 0 for _, number := range numbers { total += number } return total } sum(2, 5, 7, 10) // 24 -
First-class functions:
In Go, functions are first-class values. They can be assigned to variables, passed as arguments, and returned from functions.
func main() { price := 100.00 fmt.Printf("Regular user price: $%.2f\n", applyDiscount(price, regularDiscount)) // Regular user price: $90.00 fmt.Printf("VIP user price: $%.2f\n", applyDiscount(price, vipDiscount)) // VIP user price: $80.00 } func vipDiscount(price float64) float64 { return price * 0.8 } func regularDiscount(price float64) float64 { return price * 0.9 } func applyDiscount(price float64, strategy func(float64) float64) float64 { return strategy(price) } -
Anonymous functions:
Anonymous functions are functions without a name.
func main() { func() { fmt.Println("Hello, world!") }() // immediately invoking the anonymous function }-
Assigning an anonymous function to a variable:
myVar := func(a, b int) { fmt.Println(a + b) } myVar(1, 2) // 3
-
Pass-by Value & Reference
In Go, all function arguments are passed by value. The difference is in what gets copied.
-
Pass-by Value:
With pure value types, Go copies the actual data. The function works with its own copy, so any change made inside the function stays local and does not affect the original.
func increment(num int) { num++ fmt.Println("Inside:", num) // Inside: 6 } func main() { num := 5 increment(num) fmt.Println("Outside:", num) // Outside: 5 }Pure Value Types (
pass-by-value): Booleans, Numerics, Arrays, Structs.a := [3]int{2, 4, 6} // array b := a b[1] = 99 fmt.Println(a, b) // [2 4 6] [2 99 6] -
Pass-by Reference:
With reference-containing types, Go copies the value, but that value contains a reference to shared underlying data. So changes inside the function affect that shared data.
func change(s []int) { s[0] = 99 } func main() { nums := []int{1, 2, 3} fmt.Println(nums) // [1 2 3] change(nums) fmt.Println(nums) // [99 2 3] }Reference-Containing Types (
pass-by-reference): Strings (immutable), Slices, Maps, Channels, Pointers (&x).a := []int{2, 4, 6} // slice b := a b[1] = 99 fmt.Println(a, b) // [2 99 6] [2 99 6]Even when a value allows modification of shared underlying data, the variable itself is still passed by value. Reassigning the parameter inside a function only changes the local copy and does not affect the original variable in the caller.
func reassignSlice(s []int) { s = []int{100, 200, 300} s[0] = 99 } func main() { nums := []int{1, 2, 3} fmt.Println("Before:", nums) // Before: [1 2 3] reassignSlice(nums) fmt.Println("After:", nums) // After: [1 2 3] }another example:
func addItem(s []int, v int) { s = append(s, v) } func main() { nums := []int{1, 2, 3} fmt.Println("Before:", nums) // Before: [1 2 3] addItem(nums, 4) fmt.Println("After:", nums) // After: [1 2 3] }
Closures
Closures are functions that capture and use variables from their surrounding scope, even after that scope has finished executing.
Examples:
-
func sum() func(int) int { total := 0 return func(number int) int { total += number return total } } func main() { add1 := sum() add2 := sum() fmt.Println(add1(1)) // 1 fmt.Println(add1(2)) // 3 fmt.Println(add1(3)) // 6 fmt.Println(add2(1)) // 1 fmt.Println(add2(2)) // 3 fmt.Println(add2(6)) // 9 }Each call to
sum()returns a new closure with its own independent variables (total), allowingadd1andadd2to maintain separate internal state. -
func fibonacci() func() int { a, b := 0, 1 return func() int { a, b = b, a+b return a } } func main() { f := fibonacci() for range 10 { fmt.Println(f()) } } // 1 // 1 // 2 // 3 // 5 // 8 // 13 // 21 // 34 // 55
The defer Keyword
The defer keyword allows us to execute a function or line of code at the end of the current function's execution.
func main() {
defer fmt.Println("done")
fmt.Println("work")
}
// work
// doneExtra:
-
deferdepends only on the surrounding function scope:deferis registered when execution reaches that line, but it runs when the surrounding function ends. It does not run when a block such asiforforends, even if it is inside them.func main() { fmt.Println("start") if true { defer fmt.Println("inside if") fmt.Println("in block") } fmt.Println("end of function") } // start // in block // end of function // inside if -
Multiple defers:
Multiple deferred statements are executed in last-in-first-out (LIFO) order. This means that the most recently deferred function is executed first.
func main() { defer sayBye() fmt.Println("1") defer fmt.Println("2") fmt.Println("3") fmt.Println(myFunc()) } func myFunc() string { defer fmt.Println("4") return "5" } func sayBye() { fmt.Println("bye") } // 1 // 3 // 4 // 5 // 2 // bye