Go function
last modified April 11, 2024
In this article we show how to work with functions in Golang.
Function definition
A function is a mapping of zero or more input parameters to zero or more output parameters.
The advantages of using functions are:
- Reducing duplication of code
- Decomposing complex problems into simpler pieces
- Improving clarity of the code
- Reuse of code
- Information hiding
Go functions are first-class citizens. Functions can be assigned to variables, passed as arguments to functions or returned from functions. This makes the language more flexible.
Functions in Go are created with the func
keyword. We use the
return
keyword to return values from functions. The body of
the function consists of statements that are executed when the function is
called. The body is delimited with a pair of curly brackets {}
.
To call a function, we specify its name followed by round bracktets
()
. A function may or may not take parameters.
$ go version go version go1.22.2 linux/amd64
We use Go version 1.22.2.
Simple example
The following example creates a simple function in Go.
package main import "fmt" func main() { x := 4 y := 5 z := add(x, y) fmt.Printf("Output: %d\n", z) } func add(a int, b int) int { return a + b }
In the code example, we define a function which adds two values.
z := add(x, y)
We call the add
function; it takes two parameters. The computed
value is passed to the z
variable.
func add(a int, b int) int { return a + b }
We define the add
function. The parameters of the function are
separated with comma; each parameter name is followed with its data type.
After the parameters, we specify the return value type. The statements that are
executed when the function is called are placed between curly brackets.
The result of the addition operation is returned to the caller with the
return
keyword.
$ go run main.go Output: 9
Omitting type
When the parameters of a function have the same type, the type can be omitted for some of them; that is, specified only once.
package main import "fmt" func add(x int, y int) int { return x + y } func sub(x, y int) int { return x - y } func main() { fmt.Println(add(5, 4)) fmt.Println(sub(5, 4)) }
In the code example, we have two functions: add
and sub
.
In case of the sub
function, the type was omitted for the
x
variable.
Function named return variables
We can specify named return variables in round brackets after the function parameters.
package main import "fmt" func inc(x, y, z int) (a, b, c int) { a = x + 1 b = y + 1 c = z + 1 return } func main() { x, y, z := inc(10, 100, 1000) fmt.Println(x, y, z) }
In the code example, we have a function which increments its three parameters.
func inc(x, y, z int) (a, b, c int) {
We have three named return values: a
, b
, and c
.
a = x + 1 b = y + 1 c = z + 1 return
We compute the values for the return variables. After that, we must specify the
return
keyword.
$ go run main.go 11 101 1001
Multiple return values
Go functions allow to return multiple values.
package main import ( "fmt" "math/rand" ) func threerandom() (int, int, int) { x := rand.Intn(10) y := rand.Intn(10) z := rand.Intn(10) return x, y, z } func main() { r1, r2, r3 := threerandom() fmt.Println(r1, r2, r3) }
In the code example, we have a threerandom
function, which returns
three random values.
func threerandom() (int, int, int) {
We specify that the function returns three integer values.
x := rand.Intn(10) y := rand.Intn(10) z := rand.Intn(10)
We compute three random values.
return x, y, z
The values are returned from the function. They are separated with commad character.
$ go run main.go 0 8 0
Anonymous function
We can create anonymous functions. Anonymous functions do not have a name.
package main import "fmt" func main() { sum := func(a, b, c int) int { return a + b + c }(3, 5, 7) fmt.Println("5+3+7 =", sum) }
We create an anonymous function which adds three values. We pass three parameters to the function right after its definition.
$ go run main.go 5+3+7 = 15
Variadic function
A variadic function can accept variable number of parameters. For instance, when we want to calculate the sum of values, we might have four, five, six etc. values to pass to the function.
We use the ...
(ellipses) operator to define a variadic function.
package main import "fmt" func main() { s1 := sum(1, 2, 3) s2 := sum(1, 2, 3, 4) s3 := sum(1, 2, 3, 4, 5) fmt.Println(s1, s2, s3) } func sum(nums ...int) int { res := 0 for _, n := range nums { res += n } return res }
In the code example, we have a sum
function which accepts variable
number of parameters.
func sum(nums ...int) int { res := 0 for _, n := range nums { res += n } return res }
The nums
variable is a slice, which contains all values passed
to the sum
function. We loop over the slice and calculate the
sum of the parameters.
$ go run main.go 6 10 15
Recursive function
Recursion, in mathematics and computer science, is a way of defining methods in which the method being defined is applied within its own definition. To put it differently, a recursive method calls itself to do its task. Recursion is a widely used approach to solve many programming tasks.
A typical example is the calculation of a factorial.
package main import "fmt" func fact(n int) int { if n == 0 || n == 1 { return 1 } return n * fact(n-1) } func main() { fmt.Println(fact(7)) fmt.Println(fact(10)) fmt.Println(fact(15)) }
In this code example, we calculate the factorial of three numbers.
return n * fact(n-1)
Inside the body of the fact
function, we call the fact
function with a modified argument. The function calls itself.
$ go run main.go 5040 3628800 1307674368000
These are the computed factorials.
Deferring function call
The defer
statement defers the execution of a function until the
surrounding function returns. The deferred call's arguments are evaluated
immediately, but the function call is not executed until the surrounding
function returns.
package main import "fmt" func main() { fmt.Println("begin main") defer sayHello() fmt.Println("end main") } func sayHello() { fmt.Println("hello") }
In the code example, the sayHello
function is called after the
main
function finishes.
$ go run main.go begin main end main hello
Passing parameters by value
In Go, function parameters are passed only by value.
In the following example, an integer and a User
structure are
passed as parameters to functions.
package main import "fmt" type User struct { name string occupation string } func main() { x := 10 fmt.Printf("inside main %d\n", x) inc(x) fmt.Printf("inside main %d\n", x) fmt.Println("---------------------") u := User{"John Doe", "gardener"} fmt.Printf("inside main %v\n", u) change(u) fmt.Printf("inside main %v\n", u) } func inc(x int) { x++ fmt.Printf("inside inc %d\n", x) } func change(u User) { u.occupation = "driver" fmt.Printf("inside change %v\n", u) }
In the code example, the original values of the x
and User
struct are not modified.
func inc(x int) { x++ fmt.Printf("inside inc %d\n", x) }
A copy of the integer value is created. Inside the function, we increment the value of this copy. So the original variable is intact.
$ go run main.go inside main 10 inside inc 11 inside main 10 --------------------- inside main {John Doe gardener} inside change {John Doe driver} inside main {John Doe gardener}
In the next example, we pass pointers to the the integer variable and structure.
package main import "fmt" type User struct { name string occupation string } func main() { x := 10 fmt.Printf("inside main %d\n", x) inc(&x) fmt.Printf("inside main %d\n", x) fmt.Println("---------------------") u := User{"John Doe", "gardener"} fmt.Printf("inside main %v\n", u) change(&u) fmt.Printf("inside main %v\n", u) } func inc(x *int) { (*x)++ fmt.Printf("inside inc %d\n", *x) } func change(u *User) { u.occupation = "driver" fmt.Printf("inside change %v\n", *u) }
Now the original values are modified. But technically, parameters are still passed by value. Go creates new copies of the pointers. (This is a different from C.)
inc(&x)
With the &
character, we pass a pointer to the x
variable.
func inc(x *int) { (*x)++ fmt.Printf("inside inc %d\n", *x) }
A copy of the pointer to the x
variable is created.
Changing the value of the x
modifies the original variable as well.
$ go run main.go inside main 10 inside inc 11 inside main 11 --------------------- inside main {John Doe gardener} inside change {John Doe driver} inside main {John Doe driver}
The original values have been modified.
Arrays are value types, slices and maps are reference types. So in case of the slices and maps, a copy of the reference is created.
package main import "fmt" func main() { vals := []int{1, 2, 3, 4, 5} fmt.Printf("%v\n", vals) square(vals) fmt.Printf("%v\n", vals) } func square(vals []int) { for i, val := range vals { vals[i] = val * val } }
In the code example, we pass a slice to the square
function. The
elements of the original slice are modified.
$ go run main.go [1 2 3 4 5] [1 4 9 16 25]
The elements are squared.
Arrays are value types.
package main import "fmt" func main() { vals := [5]int{1, 2, 3, 4, 5} fmt.Printf("%v\n", vals) square(vals) fmt.Printf("%v\n", vals) } func square(vals [5]int) { for i, val := range vals { vals[i] = val * val } }
The example passes an array to the square
function.
$ go run main.go [1 2 3 4 5] [1 2 3 4 5]
The elements are not modified.
Maps are reference types.
package main import "fmt" func main() { items := map[string]int{"coins": 1, "pens": 2, "chairs": 4} fmt.Printf("%v\n", items) update(items) fmt.Printf("%v\n", items) } func update(items map[string]int) { items["coins"] = 6 }
The example passes a map to the update
function.
$ go run main.go map[chairs:4 coins:1 pens:2] map[chairs:4 coins:6 pens:2]
As we can see, the original map has been updated.
Function as a parameter
A Go function can be passed to other functions as a parameter. Such a function is called a higher-order function.
package main import "fmt" func inc(x int) int { x++ return x } func dec(x int) int { x-- return x } func apply(x int, f func(int) int) int { r := f(x) return r } func main() { r1 := apply(3, inc) r2 := apply(2, dec) fmt.Println(r1) fmt.Println(r2) }
In the code example, the apply
function takes the inc
and
dec
functions as parameters.
func apply(x int, f func(int) int) int {
We specify that the second parameter is a function type.
r1 := apply(3, inc) r2 := apply(2, dec)
We pass the inc
and dec
functions to the apply
function as parameters.
$ go run main.go 4 1
Custom function types
Go allows to create reusable functions signatures with the type
keyword.
package main import "fmt" type output func(string) string func hello(name string) string { return fmt.Sprintf("hello %s", name) } func main() { var f output f = hello fmt.Println(f("Peter")) }
With the type
keyword, we create a function type which accepts
one string parameter and returns a string.
The filter function
We have a practical example where we filter data.
package main import "fmt" type User struct { name string occupation string married bool } func main() { u1 := User{"John Doe", "gardener", false} u2 := User{"Richard Roe", "driver", true} u3 := User{"Bob Martin", "teacher", true} u4 := User{"Lucy Smith", "accountant", false} u5 := User{"James Brown", "teacher", true} users := []User{u1, u2, u3, u4, u5} married := filter(users, func(u User) bool { if u.married == true { return true } return false }) teachers := filter(users, func(u User) bool { if u.occupation == "teacher" { return true } return false }) fmt.Println("Married:") fmt.Printf("%v\n", married) fmt.Println("Teachers:") fmt.Printf("%v\n", teachers) } func filter(s []User, f func(User) bool) []User { var res []User for _, v := range s { if f(v) == true { res = append(res, v) } } return res }
We have a slice of User
structures. We filter the slice to
form new slices of married users and users that are teachers.
married := filter(users, func(u User) bool { if u.married == true { return true } return false })
We call the filter
function. It accepts an anonymous function as a
parameter. The function returns true
for married users. A function
that returns a boolean value is known also as a predicate.
func filter(s []User, f func(User) bool) []User { var res []User for _, v := range s { if f(v) == true { res = append(res, v) } } return res }
The filter
function forms a new slice for all users that satisfy
the given condition.
$ go run main.go Married: [{Richard Roe driver true} {Bob Martin teacher true} {James Brown teacher true}] Teachers: [{Bob Martin teacher true} {James Brown teacher true}]
Closures
A closure in Go is an anonymous function returned from an enclosing function. Closure retains a reference to a variable defined outside its body.
package main import "fmt" func intSeq() func() int { i := 0 return func() int { i++ return i } } func main() { nextInt := intSeq() fmt.Println(nextInt()) fmt.Println(nextInt()) fmt.Println(nextInt()) fmt.Println(nextInt()) nextInt2 := intSeq() fmt.Println(nextInt2()) }
We have the intSeq
function, which generates a sequence of integers.
It returns a closure which increments the i
variable.
func intSeq() func() int {
The intSeq
is a function which returns a function which retruns
an integer.
func intSeq() func() int { i := 0 return func() int { i++ return i } }
Variables defined in functions have a local function scope. However, in this case,
the closure is bound to the i
variable even after the intSeq
function returns.
nextInt := intSeq()
We call the intSeq
function. It returns a function which will
increment a counter. The closure is stored in the nextInt
variable.
fmt.Println(nextInt()) fmt.Println(nextInt()) fmt.Println(nextInt()) fmt.Println(nextInt())
We call the closure several times.
$ go run main.go 1 2 3 4 1
Higher-order functions
A higher-order function is a function which either accepts a function as a parameter or returns a function.
package main import "fmt" func main() { x := 3 y := 4 add, sub := getAddSub() r1, r2 := apply(x, y, add, sub) fmt.Printf("%d + %d = %d\n", x, y, r1) fmt.Printf("%d - %d = %d\n", x, y, r2) } func apply(x, y int, add func(int, int) int, sub func(int, int) int) (int, int) { r1 := add(x, y) r2 := sub(x, y) return r1, r2 } func getAddSub() (func(int, int) int, func(int, int) int) { add := func(x, y int) int { return x + y } sub := func(x, y int) int { return x - y } return add, sub }
In the code example, we have some complex operations with functions. The
apply
function takes two functions as parameters. The
getAddSub
function returns two functions.
$ go run main.go 3 + 4 = 7 3 - 4 = -1
Source
The Go Programming Language Specification
In this article we have covered functions in Golang.
Author
List all Go tutorials.