Skip to content

Go (Golang) 8 - Functions

Published:

In this tutorial, we’ll cover functions a fundamental part of Go that helps organize and reuse code efficiently.

Writing functions in Go

Basic Function Syntax

Here’s how you write a simple function in Go:

func sayHello() {
    fmt.Println("Hello, Go!")
}

This function doesn’t take any arguments or return a value. It just prints a message.

You can call it like this:

sayHello()
sayHello()
sayHello()

This will print “Hello, Go!” three times.

Adding Parameters and Returning Values

Functions can accept arguments and return values. In Go, you must specify the types of both the arguments and the return values.

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

This function takes two integers and returns their sum.

add(2, 5) // will return 7

Multiple Arguments of the Same Type

If you have multiple arguments of the same type you only need to write the type one time.

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

Multiple Return Values

In Go, functions can return multiple values:

func divide(a, b float64) (float64, error) {
	if b == 0 {
		return 0, errors.New("division by zero is not allowed")
	}
	return a / b, nil
}

This divide function returns both a result and an error. If the divisor is zero, it returns an error; otherwise, it returns the result and error is set to nil.

It’s a very common pattern in Go that the last return value is an error.

When used it would look something like this.

result, err := divide(10, 0)
if err != nil {
	fmt.Println("Error:", err)
} else {
	fmt.Println("Result:", result)
}

Passing functions as an argument

Go allows functions to be passed as arguments:

func apply(slice []int, fn func(int) int) {
	for i, v := range slice {
		slice[i] = fn(v)
	}
}

func double(n int) int {
	return n * 2
}

nums := []int{1, 2, 3, 4, 5}
apply(nums, double)
fmt.Println(nums)  // Output: [2, 4, 6, 8, 10]

In this example, the map function loops through a slice and applies the Double function to each element.

The double function could be replaced with any other function. Here is an example of square.

func apply(slice []int, fn func(int) int) {
	for i, v := range slice {
		slice[i] = fn(v)
	}
}

func square(n int) int {
	return n * n
}

nums := []int{1, 2, 3, 4, 5}
Apply(nums, square)
fmt.Println(nums)  // Output: [1, 4, 9, 16, 25]

Variadic Functions

Variadic functions allow you to pass any number of arguments. One example of that is fmp.println. Let’s create our own function.

func sum(nums ...int) int {
    total := 0

    for _, num := range nums {
        total += num
    }

	return total
}

This function will add together all values you pass and return the sum of them.

package main

import "fmt"

sum(100, 15, 20, 5, 7)
numbers := []int{5,8,10,20}
sum(numbers...)

In the first example we call sum with any numbers of arguments and inside the sum function Go will turn it into a slice.

You can also pass a slice as argument but you have to use three dots after it. This will unpack the slice and pass it in as separate arguments

Anonymous Function

an anonymous function is a nameless function defined inline, often used for short, one-off tasks. It can be called immediately or assigned to a variable:

func() { fmt.Println("Hello!") }()

Or assigned:

greet := func(name string) { fmt.Println("Hello,", name) }
greet("Go")

Closure

A closure is a function that remembers variables from its surrounding scope, even after the outer function has returned.

Let’s take a look at a simple closure:

// counter is a function that returns another function, which has a closure
func counter() func() int {
    x := 0 // x is captured by the returned function
    return func() int {
        x++ // Each call to this function will increment x
        return x
    }
}

increment := counter()

fmt.Println(increment()) // Output: 1
fmt.Println(increment()) // Output: 2
fmt.Println(increment()) // Output: 3

// Each time increment is called, it remembers the value of x

In this example, the increment function “remembers” the value of x between calls, thanks to the closure.

Named Return Values and Blank Returns

Go allows you to name your return values. This creates variables that will be used as return values. The inital value of a named return value is it empty value. So int is zero and string is empty string and so on.

func add(a, b int) (sum int) {
    sum = a + b
    return
}

fmt.Println(add(5,2)) // 7

Here we create a named return value called sum. As you can see the return statement is blank. Go will automatically return the named return values when using blank return statement.

if we don’t give sum a value it will return 0.

func add(a, b int) (sum int) {
    return
}

fmt.Println(add(5,2)) // 0

My advice is to only use it for very simple functions or not use them at all. If you use it for more complicated functions it becomes very hard to read and keep track of the data flow. Since they are initialized to their zero values you also run the risk of returning unintended results.

Here is one example of a more complicated function.

func complicatedFunction(a, b int) (result int, err error) {
    if a < 0 {
        err = errors.New("a is negative")
        return
    }
    result = a + b
    if result > 100 {
        err = errors.New("too large")
        return
    }
    // more logic
    return
}

As you can see it becomes hard to keep track of what is being returned.

Conclusion

Functions in Go are flexible, allowing you to define reusable code, handle errors, and pass or return other functions. They help make your programs clean and maintainable. I hope you enjoyed this tutorial see you in the next one.

Full source code can be found here Github