Functions are blocks of code that take zero or more inputs, process these inputs, and produce zero or more outputs. Functions help us divide our program into small or medium-sized reusable pieces of code, thus improving readability and maintainability and reducing the complexity of our Go program.
In this topic, we'll learn about function declarations, how to call functions, and also how to pass data to functions in Go.
Basic syntax
In Go, a function's signature has the following structure:
It starts with the keyword
func, followed by the name of the function;Then we have a set of parentheses
(...)known as the parameter list — it holds zero or more parameters used to import variables into the function;After the parameter list, we have the return type of the function — it can be of a single type, of various types, or be empty as well;
At the end of the function declaration, we have the code block of the function enclosed in curly brackets
{}.
Let's take a look at the declaration of the hello() function:
func hello(name string) string {
return "Hello " + name + "!"
}In the first line, we can see the func keyword, followed by hello, which is the name of the function. After the function's name, we have the parameter list that takes only one parameter called name of the string type, and the function's return type string. Collectively, the function's name, the parameter list, and the return type are known as the function's signature.
Calling a function and passing arguments
Now that we understand the basic syntax of Go functions, let's explore two different ways we can call the hello() function within our main function and pass a string value as an argument to the message parameter:
package main
import "fmt"
func hello(message string) string {
return "Hello, " + message
}
func main() {
// Option #1 - Assign the return value of a function call to a variable.
// Here we assign to greeting the return value of the hello() function call:
greeting := hello("I was called via Option #1!")
// Call the greeting variable within the Println function:
fmt.Println(greeting) // Hello, I was called via Option #1!
}In option #1, we assign the return value of the hello() function call to the greeting variable. In this case, the return value of hello() is a concatenated string "Hello, " + the value: "I was called via Option #1!" that we passed to hello() as an argument.
Then, we call the greeting variable within the fmt.Println() function to finally print the return value of the hello() function.
// Option #2 - Call the hello function with a string value directly within the Println function:
fmt.Println(hello("I was called via Option #2!")) // Hello, I was called via Option #2!In option #2, we take a more direct approach. We call the hello() function directly within the fmt.Println() function with the string value "I was called via Option #2!". In both cases, the string values are passed as arguments to the message parameter of the hello() function. In simple terms, an argument is a value or variable sent to the function when it is called.
Passing arguments by pointers
Go allows us to pass arguments to our function by values or pointers; passing arguments by pointers is also known as passing by reference. In contrast to passing by value, which makes a copy of the data we are passing to the function, passing by reference makes a copy of the pointer but not of the data it points to.
To further explain the difference between passing arguments by values or passing by reference, let's compare two functions: realSwap(), which takes two pointer-to-integer values as arguments, and the fakeSwap() function, which takes two integer values as arguments.
Let's take a look at the realSwap() function first:
package main
import "fmt"
func realSwap(x *int, y *int) {
// directly exchange the value stored at address x (42)
// with the value stored at address y (27)
*x, *y = *y, *x
}
func main() {
var num1 = 42
var num2 = 27
fmt.Println("Before swapping values: x =", num1, "and y =", num2)
realSwap(&num1, &num2) // pass the address of num1 and num2 to realSwap
fmt.Println("After swapping values: x =", num1, "and y =", num2)
}
// Output:
// Before swapping values: x = 42 and y = 27
// After swapping values: x = 27 and y = 42In the example above, we call the function realSwap() and pass the address of num1 and num2 as parameters, since realSwap() takes two pointer-to-integer *int values as arguments. Within the code block of realSwap(), we directly exchange the values of num1 and num2 via the *x, *y = *y, *x syntax.
If we tried the same procedure with the function fakeSwap() that takes two integer values instead of pointers, the values of num1 and num2 would not get updated. Let's take a look at the declaration of fakeSwap():
func fakeSwap(x int, y int) { // fakeSwap takes two integer types as arguments
x, y = y, x
}Now let's call fakeSwap() within our main function and check the values of num1 and num2 after executing our program:
fmt.Println("Before swapping values: x = ", num1, "and y =", num2)
fakeSwap(num1, num2) // pass integer values num1 and num2 to fakeSwap
fmt.Println("After swapping values: x = ", num1, "and y =", num2)
// Output:
// Before swapping values: x = 42 and y = 27
// After swapping values: x = 42 and y = 27We can see that the values of num1 and num2 have not been swapped: they've stayed the same. This happens because fakeSwap() receives and manipulates a private copy of the two integers we passed to it and stores them in different memory locations. Therefore, any changes made inside fakeSwap() are not reflected in the actual parameters of the caller. In contrast, when we call realSwap() and pass to it the pointers *x and *y, the values of num1 and num2 are properly exchanged. Taking the address of the variables explicitly as arguments allows realSwap() to mutate and swap them.
Conclusion
Let's briefly remember what we've learned on this topic:
Collectively, a function's name, parameters, and return type are known as the function's signature;
We can assign a function call to a variable and then use that variable in the
mainfunction, or we can call a function directly by its name within themainfunction;We can pass arguments to a function by values or by pointers (also known as passing by reference).