Functions basically accept values and return values. They are the basic unit of abstraction in Go.
Abstraction is the concept of hiding what happens in the background in order to simplify one’s view of a system. An abstraction is a simplified view of a system which omits unimportant details.
For example, we do not have to know how the Println function in the fmt package actually works to add characters to the console, in order to use Println. This means that the Println function is an abstraction (a simplified view) of the entire process that sends letters and other characters to the console.
Arguments
These are the values that we pass into a function. A function can have zero, one or more arguments. Here is an example of a function that accepts a string and informs the user if that string contains the letter a:
package main
import (
"fmt"
"strings"
)
func ContainsA(str string) {
if strings.Contains(str, "a") {
fmt.Println("The string", str, "contains the letter 'a'")
} else {
fmt.Println("The string", str, "does not contain the letter 'a'")
}
}
func main() {
ContainsA("Hello")
ContainsA("Hello back")
}
This function only has one argument. We can modify the function to accept two arguments, one for the string being checked and the other for the letter we are checking for:
package main
import (
"fmt"
"strings"
)
func ContainsLetter(str string, letter string) {
if strings.Contains(str, letter) {
fmt.Println("The string", str, "contains the letter", letter)
} else {
fmt.Println("The string", str, "does not contain the letter", letter)
}
}
func main() {
ContainsLetter("Hello", "o")
ContainsLetter("Hello back", "e")
}
The power of functions comes from their flexibility. We can name them to make our code more readable and they allow us to avoid having to rewrite the same code over and over again.
Return values
A function is replaced by its return value(s) at runtime. Functions in Go can return multiple values. Here is an example of a function that returns a timestamp for the current time as a string:
package main
import (
"fmt"
"time"
)
func GetCurrentTimestamp() string {
return time.Now().Format(time.RFC3339)
}
func main() {
now := GetCurrentTimestamp()
fmt.Println("The current timestamp is", now)
}
The variable now is used to store the return value of the function (a string). The compiler will infer the datatype of now because it knows the return type of the function GetCurrentTimestamp is string.
When a function returns multiple values, we wrap the return types in parentheses:
package main
import "fmt"
func MinMax(numbers []int) (int, int) {
min := numbers[0]
max := numbers[0]
for _, number := range numbers {
fmt.Println(number)
if number < min {
min = number
}
if number > max {
max = number
}
}
return min, max
}
func main() {
a, b := MinMax([]int{1, -342, 41, 2, 676, 54})
fmt.Println("min:", a)
fmt.Println("max:", b)
_, c := MinMax([]int{3, -3, 4})
fmt.Println("c:", c)
}
This function accepts a slice of integers and returns two integers, the first being the minimum value and the second being the maximum value. In the main function, the first return value is stored in a variable a and the second in b. We can use an underscore, _ if we do not intend to use every return value of a function. Here we store just the maximum value in a variable c.
Many of the functions you have seen so far have names/identifiers that begin with a capital letter. These functions could have been named with a common letter but they would not be accessible from other packages. This is true for the main package we are using. Println begins with a capital letter in the fmt package because the creators of that package intend for us to use it in our own custom packages and modules.
Functions as modules/units of modular design
We do not need to know the implementation of the function in order to use the interface that the function provides to us. Anything with an interface and implementation is a module.
The interface is a contract between a system and the environment using that system. A keyboard, for example, is an interface - it allows us (the environment) to enter information into our computer (the system being used) as long as we follow the contract. That is, we use the keys on the keyboard as intending by the designer.
We do not need to understand how a keyboard operates mechanically or how the keyboard interacts with the operating system to fill letters and symbols into a text editor program, before we can use the keyboard to do meaningful work.
Functions in a similar fashion allow us to hide unnecessary/unimportant details of algorithms and data structures so that we can focus on other tasks. We may want to find out if a string contains a certain substring in order to determine if we should grant a user access to certain features in our app. We would want to focus on implementing the latter rather than having to figure out if the substring is present in the string so we simply call strings.Contains without worrying about how it implements that checking algorithm.