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 becausea
andb
are ofint
data type whilec
is offloat32
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 becauseadd
andmult
are ofint
data type whilediv
is offloat32
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 1
eventually.
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 downdeferred functions
to 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
calc
function instead of taking the third argument as function takestring
command likeadd
orsubstract
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 likefunction
,pointer
,slice
,interface
,channel
,map
, etc. You can read aboutnil
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.