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 becauseaandbare ofintdata type whilecis offloat32data 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 becauseaddandmultare ofintdata type whiledivis offloat32data 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
stackI am referring to. A stack is like a notebook where Go compiler writes downdeferred functionsto execute at the end of current function execution. This stack followsLast 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
calcfunction instead of taking the third argument as function takestringcommand likeaddorsubstractand 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>
💡
nilis a zero value for many types likefunction,pointer,slice,interface,channel,map, etc. You can read aboutnilfrom 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.