The anatomy of Functions in Go

Like JavaScript, functions in Go are first-class citizens. They can be assigned to variables, passed as an argument, immediately invoked or deferred for last execution.

What is a function?

A function, in general, is a small piece of code that is dedicated to a perform particular task based on some input values. We create a function so that we can perform such an operation whenever we want from wherever we want by providing some input values.

These input values provide extra information to a function and they are totally optional. A function’s execution may or may not return any result.

In Go, a function is defined using func keyword.

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

This function can be called from anywhere inside a function body in the program. For example, we have a dosomething function that prints some characters to the standard output.

package main

import "fmt"

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

func main() {
	dosomething()
}

// Hello World!

Naming conventions

Go recommends writing function names in simple word or camelCase. Even under_score function names are valid, but they are not idiomatic in Go.

Function parameters

As discussed earlier, a function may take input values for its execution. These input values are provided in a function call, called arguments. One or multiple arguments can also be passed to a function.

Example 1. Print greeting message

package main

import "fmt"

func greet(user string) {
	fmt.Println("Hello " + user)
}

func main() {
	greet("John Doe")
}

// Hello John Doe

Example 2. Add two integers

package main

import "fmt"

func add(a int, b int) {
	c := a + b
	fmt.Println(c)
}

func main() {
	add(1, 5)
}

// 6

You can use shorthand parameter notation in case multiple parameters in succession are of the same data type.

package main

import "fmt"

func add(a, b int) {
	c := a + b
	fmt.Println(c)
}

func main() {
	add(1, 5)
}

// 6

💡 func add(a, b int, c float32) is also a valid syntax because a and b are of int data type while c is of float32 data type.

Return value

A function can also return a value that can be printed or assigned to another variable.

package main

import "fmt"

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

func main() {
	result := add(1, 5)
	fmt.Println(result)
}

// 6

In case a function returns a value, you must specify the data type of a return value just after the function parameter parentheses.

In the above program, we made sure that return value matches the return type of a function by converting the type of result (originally int) into int64.

Multiple return values

Unlike other programming languages, Go can return multiple values from the function. In this case, we must specify return types of the values (just like above) inside parentheses just after the function parameter parentheses.

package main

import "fmt"

func addMult(a, b int) (int, int) {
	return a + b, a * b
}

func main() {
	addRes, multRes := addMult(2, 5)
	fmt.Println(addRes, multRes)
}

// 7 10

To catch multiple values from a function that returns multiple values, we must specify the comma-separated variable declaration.

In case of multiple returns values but you are only interested in one single value returned by the function, you can assign other value(s) to _ (blank identifier) which stores the value to an empty variable.

💡 This is needed because if a variable is defined but not used in Go, the compiler complains about it.

package main

import "fmt"

func addMult(a, b int) (int, int) {
	return a + b, a * b
}

func main() {
	_, multRes := addMult(2, 5)
	fmt.Println(multRes)
}

// 10

Named return values

Named return values are a great way to explicitly mention return variables in the function definition itself. These variables will be created automatically and made available inside the function body. You can change the values of these variables inside a function.

A return statement at the end of the function is necessary to return named values. Go will automatically return these variables when the function hits the return statement.

package main

import "fmt"

func addMult(a, b int) (add int, mul int) {
	add = a + b
	mul = a * b

	return // necessary
}

func main() {
	addRes, multRes := addMult(2, 5)
	fmt.Println(addRes, multRes)
}

// 7 10

You can also combine named return values when they hold the same data type as below. However, when we use named return value, all named return values should be defined with their data types.

package main

import "fmt"

func addMult(a, b int) (add, mul int) {
	add = a + b
	mul = a * b

	return // necessary
}

func main() {
	addRes, multRes := addMult(2, 5)
	fmt.Println(addRes, multRes)
}

// 7 10

💡 func math() (add, mult int, div float32)is also a valid syntax because add and mult are of int data type while div is of float32 data type.

Recursive function

A function is called recursive when it calls itself from inside the body. A simple syntax for the recursive function is

func r() {
    r()
}

If we run the above function r, it will loop infinitely. Hence, in a recursive function, we generally use a conditional statement such as if-else to come out of the infinite loop.

A simple example of a recursive function is factorial of n. A simple recursive formula for factorial of n is n * (n-1)! provided n > 0.

// n! = n*(n-1)! where n>0
func getFactorial(num int) int {
	if num > 1 {
		return num * getFactorial(num-1)
	} else {
		return 1 // 1! == 1
	}
}

Above getFactorial function is recursive, as we are calling getFactorial from inside getFactorial function. Let’s understand how it works.

When getFactorial get called with a int parameter num, if num is equal to 1, function returns 1, else it goes inside if block and executes num * getFactorial(num-1).

Since, there is a function call, function getFactorial called again and return value will be kept on hold until getFactorial returns something. This stack will be kept on building until getFactorial returns something, which is 1eventually.

As soon as that happens, all call stack will be resolved one by one eventually resolving first getFactorial call.

package main

import "fmt"

// n! = n×(n-1)! where n >0
func getFactorial(num int) int {
	if num > 1 {
		return num * getFactorial(num-1)
	}

	return 1 // 1! == 1
}

func main() {
	f := getFactorial(4)
	fmt.Println(f)
}

// 24

The defer keyword

defer is a keyword in Go that makes a function executes at the end of the execution of parent function or when parent function hits return statement.

Let’s dive into the example to understand better.

package main

import "fmt"

func sayDone() {
	fmt.Println("I am done")
}

func main() {
	fmt.Println("main started")

	defer sayDone()

	fmt.Println("main finished")
}

// main started
// main finished
// I am done

As main function executes, it will print main started and then hits sayDone but keeps in the waiting list because of defer function. Then it prints main finished and as main function stops executing, sayDone() gets executed.

We can pass parameters to defer function if it supports but there is a hidden gotcha. Let’s create a simple function with arguments.

package main

import "fmt"

func endTime(timestamp string) {
	fmt.Println("Program ended at", timestamp)
}

func main() {
	time := "1 PM"

	defer endTime(time)

	time = "2 PM"

	fmt.Println("doing something")
	fmt.Println("main finished")
	fmt.Println("time is", time)
}

// doing something
// main finished
// time is 2 PM
// Program ended at 1 PM

In the above program, we deferred execution of endTime function which means it will get executed at the end of main function but since at the end main function, time === "2 PM", we were expecting Program ended at 2 PM message. Even though because of deferred execution, endTime function is executing at the end of main function, it was pushed into the stack with all available argument values earlier when time variable was still 1 PM.

💡 You may ask, what is this stack I am referring to. A stack is like a notebook where Go compiler writes down deferred functions to execute at the end of current function execution. This stack follows Last In First Out (LIFO) order of execution. Which means, any task pushed first, will execute in the end.

Let’s write multiple deferred tasks and see what I mean

package main

import "fmt"

func greet(message string) {
	fmt.Println("greeting: ", message)
}

func main() {
	fmt.Println("Call one")

	defer greet("Greet one")

	fmt.Println("Call two")

	defer greet("Greet two")

	fmt.Println("Call three")

	defer greet("Greet three")
}

// Call one
// Call two
// Call three
// greeting:  Greet three
// greeting:  Greet two
// greeting:  Greet one

Practical use of defer can be seen when a function has too many conditions whether if-else or case statements, and at the end of each condition, you need to do something like close a file or send http response. Instead of writing multiple calls, we can use defer to save the day.

Below is an example of a bad program.

if cond1 {
    ...
    fs.Close(file)
} else if cond2 {
    ...
    fs.Close(file)
} else if cond3 {
    ...
    fs.Close(file)
} else {
    ...
    fs.Close(file)
}

Below is an example of a good program.

defer fs.Close(file)
if cond1 {
    ...
} else if cond2 {
    ...
} else if cond3 {
    ...
} else {
    ...
}

Function as type

A function in Go is also a type. If two function accepts the same parameters and returns the same values, then these two functions are of the same type.

For example, add and substract which individually takes two integers of type int and return an integer of type int are of the same type.

We have seen some function definitions before, for example, built-in append function has a definition like

func append(slice []Type, elems ...Type) []Type

Hence any function, for example, prepend which adds elements at the beginning of the slice (expandable array) if has a definition like

func prepend(slice []Type, elems ...Type) []Type

Then append and prepend will have the same type of

func (slice []Type, elems ...Type) []Type

So in Go, the function body does not have anything to do with the function type. But what is the use of this type?

This can be useful if you are passing a function as an argument to another function or when a function returns another function and you need to give a return type in a function definition.

Let’s create a function add and subtract, and see how they compare.

package main

import "fmt"

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

func subtract(a int, b int) int {
	return a - b
}

func main() {
	fmt.Printf("Type of function add is			%T\n", add)
	fmt.Printf("Type of function subtract is		%T\n", subtract)
}


// Type of function add is			func(int, int) int
// Type of function subtract is		func(int, int) int

So you can see that both add and subtract function has the same type func(int, int) int.

Let’s create a function that takes two integers and third argument a function that does a mathematical operation on those two numbers. We will use add and subtract function as the third parameter to this function.

package main

import "fmt"

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

func subtract(a int, b int) int {
	return a - b
}

func calc(a int, b int, f func(int, int) int) int {
	r := f(a, b)
	return r
}

func main() {
	addResult := calc(5, 3, add)
	subResult := calc(5, 3, subtract)
	fmt.Println("5+3 =", addResult)
	fmt.Println("5-3 =", subResult)
}

// 5+3 = 8
// 5-3 = 2

In the above program, we have defined a function calc which takes to int arguments a & b and third function argument f of type func(int, int)`` int. Then we are calling f function with a and b as arguments.

We can create a derived type that will make things simpler. We can rewrite the above program as

package main

import "fmt"

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

func subtract(a int, b int) int {
	return a - b
}

type CalcFunc func(int, int) int

func calc(a int, b int, f CalcFunc) int {
	r := f(a, b) // calling add(a,b) or substract(a,b)
	return r
}

func main() {
	addResult := calc(5, 3, add)
	subResult := calc(5, 3, subtract)
	fmt.Println("5+3 =", addResult)
	fmt.Println("5-3 =", subResult)
}

// 5+3 = 8
// 5-3 = 2

💡 We could also write the above program in a different way where calc function instead of taking the third argument as function take string command like add or substract and returns an anonymous function based on the command which we then can execute.

Since we understood that a function has its own type, we can declare a variable of type function and assign it layer, like below.

var add func(int, int) int

The syntax above will declare a variable of type function which takes two int arguments and returns a int value. When you log the variable add, it will return nil. This is because function add does not have any value.

fmt.Println(add) // <nil>

💡 nil is a zero value for many types like function, pointer, slice, interface, channel, map, etc. You can read about nil from this article.

Function as value (anonymous function)

A function in Go can also be a value. This means you can assign a function to a variable.

package main

import "fmt"

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

func main() {
	fmt.Println("5+3 =", add(5, 3))
}

// 5+3 = 8

In the above program, we have created a global variable add and assigned a newly created function to it. We have used Go’s type inference to get the type of anonymous function (as we haven’t mentioned the type of add). In this case, add is an anonymous function as it was created from a function that doesn’t have a name.

Immediately-invoked function expression (IIFE)

If you came from JavaScript world, you know what immediately invoked function expression is, but no worries if you don’t. In Go, we can create an anonymous function that can be defined and executed at the same time.

As we have seen in an earlier example, an anonymous function defined as

package main

import "fmt"

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

func main() {
	fmt.Println("5+3 =", add(5, 3))
}

// 5+3 = 8

Where, add is an anonymous function. Some can argue that it’s not truly anonymous because we can still refer to the add function from anywhere in main function (in other cases, from anywhere in the program). But not in the case when a function is immediately invoked or executed. Let’s modify the previous example.

package main

import "fmt"

func main() {
	sum := func(a int, b int) int {
		return a + b
	}(3, 5)

	fmt.Println("5+3 =", sum)
}

// 5+3 = 8

In the above program, look at the function definition. The first part from func to } defines the function while later (3, 5) executes it. Hence sum is the value returned by a function execution. Hence the above program yields the following result

5+3 = 8

💡 Immediately invoked function can also be used outside main function in global context. This can be useful when you need to create a global variable using return value of a function execution and you don’t want to reveal the function to the other parts of your program.

#golang #functions