Functions let you name a piece of code and reuse it. In this lesson you’ll write your own: with parameters, with one or more return values, with a variable number of arguments, and as values you can store and pass around. You’ll also build a closure, a function that remembers state between calls.
Declaring a function
You’ve been writing one function since your first program: main. Other functions are declared the same way, with the func keyword, a name, a parameter list in parentheses and a body in braces:
package main
import "fmt"
func greet(name string) {
fmt.Println("Hello,", name)
}
func main() {
greet("Erik")
greet("gopher")
}Code language: Go (go)
Hello, Erik
Hello, gopherCode language: plaintext (plaintext)
Each parameter has a name followed by its type, the same order as in a var declaration. Functions can be declared in any order in the file; greet could just as well come after main. A function whose name starts with a capital letter is exported, visible to other packages. That matters once you write packages; for now, lowercase names are fine.
Parameters
Separate multiple parameters with commas. When consecutive parameters share a type, you can write the type once after the last one. These two declarations mean the same thing:
func describe(name string, age int, height int) {}
func describe2(name string, age, height int) {}Code language: Go (go)
Go checks every call. Pass the wrong number of arguments, or the wrong type, and the program doesn’t compile. The error message even tells you what it got and what it wanted:
package main
import "fmt"
func add(a, b int) int {
return a + b
}
func main() {
fmt.Println(add(1))
}Code language: Go (go)
./main.go:10:19: not enough arguments in call to add
have (number)
want (int, int)Code language: plaintext (plaintext)
Arguments are copies
Go passes arguments by value: the function receives a copy. Changing a parameter inside the function doesn’t change the caller’s variable. If you want a function to modify a variable, pass a pointer to it, as you saw in the variables and pointers lesson:
package main
import "fmt"
func double(n int) {
n *= 2
}
func doublePtr(n *int) {
*n *= 2
}
func main() {
x := 10
double(x)
fmt.Println(x)
doublePtr(&x)
fmt.Println(x)
}Code language: Go (go)
10
20Code language: plaintext (plaintext)
Return values
To return a value, put its type after the parameter list and use return:
package main
import "fmt"
func area(width, height float64) float64 {
return width * height
}
func main() {
a := area(3, 4.5)
fmt.Println(a)
fmt.Println(area(2, 2) + area(1, 1))
}Code language: Go (go)
13.5
5Code language: plaintext (plaintext)
A function with a result type must end every path with a return. The compiler checks this:
package main
import "fmt"
func sign(n int) string {
if n < 0 {
return "negative"
} else if n > 0 {
return "positive"
}
}
func main() {
fmt.Println(sign(3))
}Code language: Go (go)
./main.go:11:1: missing returnCode language: plaintext (plaintext)
What should sign(0) return? Go makes you decide. Adding return "zero" before the closing brace fixes it.
Multiple return values
A Go function can return more than one value. List the types in parentheses, and return the values separated by commas:
package main
import "fmt"
func divide(a, b int) (int, int) {
return a / b, a % b
}
func main() {
q, r := divide(17, 5)
fmt.Println(q, r)
q, _ = divide(9, 2)
fmt.Println(q)
}Code language: Go (go)
3 2
4Code language: plaintext (plaintext)
When you don’t need one of the values, assign it to the blank identifier _. You’ve already used functions like this: strconv.Atoi and strings.Cut from the strings lesson. By far the most common use is returning a result plus an error, which the errors lesson covers in depth.
Named results
You can give the results names. They become variables, initialized to their zero values, and a bare return (a “naked return”) returns their current values:
package main
import "fmt"
func stats(a, b, c float64) (sum, avg float64) {
sum = a + b + c
avg = sum / 3
return
}
func main() {
s, a := stats(2, 4, 9)
fmt.Println(s, a)
}Code language: Go (go)
15 5Code language: plaintext (plaintext)
Use this sparingly. Named results are good documentation when two results have the same type, because (sum, avg float64) says more than (float64, float64). Naked returns, though, make longer functions hard to read: you have to scroll up to see what’s being returned. My rule: name results when it helps the reader, and still write return sum, avg explicitly. There’s one place where named results are really needed, which you’ll see in the defer lesson.
Variadic functions
A variadic function accepts any number of arguments for its last parameter. Write ... before the type. Inside the function, the parameter is a slice you can range over:
package main
import "fmt"
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
func main() {
fmt.Println(sum())
fmt.Println(sum(1, 2, 3))
scores := []int{10, 20, 30}
fmt.Println(sum(scores...))
}Code language: Go (go)
0
6
60Code language: plaintext (plaintext)
If you already have a slice, pass it with ... after it to spread it into the arguments. fmt.Println is variadic too, which is why it takes any number of values.
Functions are values
In Go, a function is a value like any other. You can store it in a variable, pass it to another function and return it from a function. Its type is written like its signature: func(int) int is “a function that takes an int and returns an int”.
package main
import (
"fmt"
"strings"
)
func square(n int) int {
return n * n
}
func applyTwice(f func(int) int, n int) int {
return f(f(n))
}
func main() {
op := square
fmt.Println(op(5))
fmt.Println(applyTwice(square, 3))
transform := strings.ToUpper
fmt.Println(transform("shout"))
}Code language: Go (go)
25
81
SHOUTCode language: plaintext (plaintext)
Note that op := square has no parentheses: you’re storing the function, not calling it. Passing functions around is how much of the standard library works, for example sorting with a custom comparison or handling HTTP requests.
Anonymous functions
A function doesn’t need a name. You can write a function literal right where you need it, assign it to a variable, pass it directly, or even call it on the spot:
package main
import "fmt"
func applyTwice(f func(int) int, n int) int {
return f(f(n))
}
func main() {
add10 := func(n int) int {
return n + 10
}
fmt.Println(add10(1))
fmt.Println(applyTwice(func(n int) int { return n * 3 }, 2))
func() {
fmt.Println("called right away")
}()
}Code language: Go (go)
11
18
called right awayCode language: plaintext (plaintext)
The () after the last function literal calls it immediately. You’ll see that pattern with defer and goroutines.
Closures
An anonymous function can use variables from the function around it. It doesn’t get a copy; it shares the variable, and keeps it alive as long as the function value exists. A function that does this is called a closure. The classic example is a counter:
package main
import "fmt"
func makeCounter() func() int {
count := 0
return func() int {
count++
return count
}
}
func main() {
next := makeCounter()
fmt.Println(next())
fmt.Println(next())
fmt.Println(next())
other := makeCounter()
fmt.Println(other())
}Code language: Go (go)
1
2
3
1Code language: plaintext (plaintext)
makeCounter has already returned when we call next(), but its count variable lives on inside the closure. Each call to makeCounter creates a new count, so other starts again at 1. Nothing outside the closure can touch count, which makes closures a neat way to keep a bit of private state.
Recursion
A function can call itself. That’s recursion, and it fits problems that are defined in terms of smaller versions of themselves. Every recursive function needs a base case that stops it:
package main
import "fmt"
func factorial(n int) int {
if n <= 1 {
return 1 // base case
}
return n * factorial(n-1)
}
func main() {
fmt.Println(factorial(5))
fmt.Println(factorial(10))
}Code language: Go (go)
120
3628800Code language: plaintext (plaintext)
Go’s stacks grow as needed, so fairly deep recursion is fine, but forget the base case and the program eventually crashes with a stack overflow. For simple counting like this, a for loop is usually clearer. Recursion shines for tree-shaped data, like walking directories.
Practice
1. Smallest and largest. Write a variadic function minMax(first int, rest ...int) (int, int) that returns the smallest and the largest of its arguments. Requiring a first argument means it can never be called with nothing. Call it with 4, 9, -2, 7.
Show a solution
package main
import "fmt"
func minMax(first int, rest ...int) (int, int) {
lo, hi := first, first
for _, n := range rest {
if n < lo {
lo = n
}
if n > hi {
hi = n
}
}
return lo, hi
}
func main() {
lo, hi := minMax(4, 9, -2, 7)
fmt.Println("min:", lo, "max:", hi)
}Code language: Go (go)
min: -2 max: 9Code language: plaintext (plaintext)(Go also has built-in min and max functions that take any number of arguments, so in real code you’d write min(4, 9, -2, 7).)
2. A multiplier factory. Write makeMultiplier(factor int) func(int) int that returns a closure multiplying its argument by factor. Create a doubler and a tripler, and print both applied to 7.
Show a solution
package main
import "fmt"
func makeMultiplier(factor int) func(int) int {
return func(n int) int {
return n * factor
}
}
func main() {
double := makeMultiplier(2)
triple := makeMultiplier(3)
fmt.Println(double(7), triple(7))
}Code language: Go (go)
14 21Code language: plaintext (plaintext)Key takeaways
- Declare functions with
func name(params) results { ... }; consecutive parameters of the same type can share it:a, b int. - Arguments are copies. Pass a pointer when a function must change the caller’s variable.
- Functions can return several values, most often a result and an
error. Name results when it documents them, but avoid naked returns in long functions. ...Tmakes a function variadic; pass an existing slice withs....- Functions are values. Anonymous functions that use outer variables are closures and keep those variables alive.