ZetCode

Go function

last modified August 24, 2023

Go function tutorial shows how to work with functions in Golang.

Go 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:

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.18.1 linux/amd64

We use Go version 1.18.

Go function simple example

The following example creates a simple function in Go.

simple.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 simple.go 
Output: 9

Go function omit 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.

omittype.go
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.

Go function named return variables

We can specify named return variables in round brackets after the function parameters.

named.go
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 namedretvals.go 
11 101 1001

Go function multiple return values

Go functions allow to return multiple values.

multiple.go
package main

import (
    "fmt"
    "math/rand"
    "time"
)

func threerandom() (int, int, int) {

    rand.Seed(time.Now().UnixNano())
    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.

rand.Seed(time.Now().UnixNano())
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 multiple.go 
0 8 0

Go anonymous function

We can create anonymous functions. Anonymous functions do not have a name.

anonymous.go
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 anonymous.go 
5+3+7 = 15

Go 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.

variadic.go
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 variadic.go 
6 10 15

Go 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.

factorial.go
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 factorial.go 
5040
3628800
1307674368000

These are the computed factorials.

Go defer 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.

defercall.go
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 defercall.go 
begin main
end main
hello

Go pass parameters by value

In Go, parameters to functions are passed only by value.

Note: everything is passed by value in Go. When we pass a pointer to a type, a separate copy of that pointer is created. This is different from C, where in case of pointers, the same pointer is passed to the function.

In the following example, an integer and a User structure are passed as parameters to functions.

passbyval.go
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 callbyval.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.

passbyval2.go
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 callbyval2.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.

passslice.go
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 passslice.go 
[1 2 3 4 5]
[1 4 9 16 25]

The elements are squared.

Arrays are value types.

passarray.go
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 passarray.go 
[1 2 3 4 5]
[1 2 3 4 5]

The elements are not modified.

Maps are reference types.

passmap.go
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 passmap.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.

Go function as parameter

A Go function can be passed to other functions as a parameter. Such a function is called a higher-order function.

funaspar.go
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 funaspar.go 
4
1

Go custom function types

Go allows to create reusable functions signatures with the type keyword.

funtype.go
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.

Go filter function

We have a practical example where we filter data.

filtering.go
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 filtering.go 
Married:
[{Richard Roe driver true} {Bob Martin teacher true} {James Brown teacher true}]
Teachers:
[{Bob Martin teacher true} {James Brown teacher true}]

Go closure

A closure in Go is an anonymous function returned from an enclosing function. Closure retains a reference to a variable defined outside its body.

closure.go
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 closure.go 
1
2
3
4
1

Go higher-order function

A higher-order function is a function which either accepts a function as a parameter or returns a function.

higherorder.go
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 higherorder.go 
3 + 4 = 7
3 - 4 = -1

In this article we have covered functions in Golang.

Author

My name is Jan Bodnar and I am a passionate programmer with many years of programming experience. I have been writing programming articles since 2007. So far, I have written over 1400 articles and 8 e-books. I have over eight years of experience in teaching programming.

List all Go tutorials.