Golang function overview for beginners
A function is a named group of statements that together perform a specific task in a program. When using functions, the code that is using it is called the caller and the function may or may not return results to the caller. Using functions in Go programs leads to code reusability hence saving time and memory and better code readability. Go comes with built-in functions that can be called and at the same time, developers can create their own custom functions. Functions are also known as methods, sub-routines or procedures.
Golang Function syntax
Function definition consists of func keyword, function name, parameters and return values if any exists.
Example
Here,
- func - Starts the function declaration
- functionName - The name of the function. The function signature consists of the name and parameter list.
- parameter list - Parameter is a placeholder indicating that the function will use a value of a specific type. The parameter list defines the type, and number of parameters the function will need to work successfully.
- return type list - A function can return a value. The return type list refers to the types the function will return to the caller after execution. Some functions can also execute code without returning a value.
- Function body - In this part of the function, code is executed to perform the desired task.
Naming convention to declare golang function
- The name must begin with a letter and have any other letters after it , numbers included .
- Function names cannot start with a number
- Function names cannot have spaces
- Function names that start with uppercase letters will be exported to other packages. Function names that start with small letters can not be exported to other packages but can be used in the package they have been defined.
- If a function name consist of multiple words, the words that follow should start with capital letters
- Function names are case-sensitive.
Declaring and calling functions
Go code is made up of packages. The most common package is the main
package. This is a special package that is used to create executable code. The main package comes with the main()
function. The main function is an important function and it is the entry point of any go program. It does not take any arguments and does not return any type. For a Go program to run , Go automatically calls the main()
function hence no need to call the main()
function in your code.
Enough with talking , let's start writing Go functions. In your working directory, create a main.go
file and write the below code.
Example
package main
import "fmt"
func main() {
sayHello()
}
func sayHello() {
fmt.Println("Hello, world!")
}
Output
Hello, world!
Explanation
In the example , we have defined a sayHello()
function that prints on the console the text Hello, world!. We call the sayHello()
in the main function by typing sayHello()
.
sayHello()
is an example of a function that does not return a value and takes no argument(s).
In the example , we have defined a sayHello()
function that prints on the console the text Hello, world!. We call the sayHello()
in the main function by typing sayHello()
. sayHello()
is an example of a function that does not return a value.
Returning data in a golang function
Returning single value
To define a function that returns a value, the return
keyword is used and the function definition should have the type of data that should be returned to the caller.
Example
package main
import "fmt"
func main() {
greetings := sayHello()
fmt.Println(greetings)
}
func sayHello() string {
return fmt.Sprint("Hello world Go developers!")
}
Output
Hello world Go developers!
Explanation
We have defined a function
sayHello()
that takes zero arguments and returns a string data type. In the function body we return the string "Hello world Go developers!" to the caller.
In the main()
function , we call the sayHello()
function and assign the returned value to the greetings variable. The greeting variable stores the returned value.
Returning multiple values
The first difference that we’ll see between Go and other languages is that Go allows for multiple return values. There are a few changes to support multiple return values. When a Go function returns multiple values, the types of the return values are listed in parentheses, separated by commas. Also, if a function returns multiple values, you must return all of them, separated by commas. Don’t put parentheses around the returned values; that’s a compile-time error.
In the below function , the function returns a string and integer types.
Example
package main
import "fmt"
func main() {
username, age := getUserDetails()
fmt.Println("Username is: ", username)
fmt.Println("Age is: ", age)
}
func getUserDetails() (string, int) {
username := "JohnDoe"
age := 34
return username, age
}
Output
Username is: JohnDoe
Age is: 34
Explanation
getUserDetails() function signature defines two return types in round brackets
(string, int)
. For this function to work properly it must return the two types in the order they were defined. When calling the getUserDetails()
function, two variables on the left are defined to receive the two return types from the getUserDetails()
function.
_
. For example, if we weren’t going to read age
, we would write the assignment as username, _ := getUserDetails()
.Use _
whenever you don’t need to read a value that’s returned by a functionReturning an error
Go takes error handling seriously and in most cases you will have to return data and an error. Add the error type in your return part of the function. In the example we are trying to convert double quotes into integer value, that is not possible and an error will be returned.
Example
package main
import (
"fmt"
"strconv"
)
func main() {
age, err := getAge()
if err != nil {
fmt.Println("Conversion Error: ", err)
return
}
fmt.Println("Age is: ", age)
}
func getAge() (int, error) {
age := ""
ageAsInt, err := strconv.Atoi(age)
if err != nil {
return 0, err
}
return ageAsInt, nil
}
Output
Conversion Error: strconv.Atoi: parsing "": invalid syntax
Explanation
The getAge()
function returns two types, an int and error. The error type is used to define an error that the runtime will return. In our example, we try to convert “”
(double quotes) to int
which is not possible. This conversion from “”
to int raises an error that will be wrapped into the error type.
Returning a pointer
Functions can also return a pointer back to the caller. The function declaration has to indicate that the return type is a pointer by prepending the asterisk(*) operator before the return type.
Example
package main
import "fmt"
func main() {
name := getNamePointer("John Doe")
fmt.Println("Name as a pointer", name)
fmt.Println("Name is a text", *name)
}
func getNamePointer(name string) *string {
return &name
}
Output
Name as a pointer 0xc000188050
Name is a text John Doe
Explanation
getNamePointer() function takes a name string as an argument, and returns a pointer to a string, indicated by
*string
. In the function body we return a pointer by prepending *
operator before the argument to indicate that the function returns a pointer to string to the caller.
Passing arguments to golang function
Go functions can also be defined to take a list of parameters. These parameters can be passed by value or by reference.
Example
package main
import (
"fmt"
)
func main() {
response := greetings("John Doe", 32)
fmt.Println(response)
}
func greetings(name string, age int) string {
return fmt.Sprintf("Hello %s, you are %d years old.", name, age)
}
Output
Hello John Doe, you are 32 years old
Arguments passed by value
Arguments passed by value are copied and the copy used in the function. In this case changes made in the copy does not change the argument value(original value)
Example
package main
import (
"fmt"
)
func main() {
defaultName := "John Doe"
response := greetings(defaultName)
fmt.Println(response)
fmt.Println(defaultName)
}
func greetings(name string) string {
name = "Mary Doe"
return fmt.Sprintf("Hello %s.", name)
}
Output
Hello Mary Doe.
John Doe
Arguments passed by reference
Passing by reference means passing a memory address to the function as an argument. This address can then be used to access the actual value used in the call. Therefore changes made to the argument in the function affects the variable outside the function.
To indicate that the function takes an address as an argument, the argument type is prepended with the asterisk(*)
operator.
Example
package main
import (
"fmt"
)
func main() {
defaultName := "John Doe"
fmt.Println("Old name :", defaultName)
changeName(&defaultName)
fmt.Println("New name :", defaultName)
}
func changeName(name *string) {
*name = "Mary Doe"
}
Output
Old name : John Doe
New name : Mary Doe
Variadic Input Parameters and Slices
We’ve been using fmt.Println
to print results to the screen and you’ve probably noticed that it allows any number of input parameters. How does it do that?
Like many languages, Go supports variadic parameters. The variadic parameter must be the last (or only) parameter in the input parameter list. You indicate it with three dots (…
) before the type. The variable that’s created within the function is a slice of the specified type. You use it just like any other slice
Example
package main
import "fmt"
func main() {
printTechStack("Go", "Gin", "gRPC", "Docker", "Kubernetes", "AWS")
}
func printTechStack(names ...string) {
for _, techName := range names {
fmt.Printf("===> %s \n", techName)
}
}
Output
===> Go
===> Gin
===> gRPC
===> Docker
===> Kubernetes
===> AWS
Go Methods
Methods are functions that contain a receiver argument in their function signature. Receiver arguments are used to access the properties of the receiver. The receiver can be a struct or non-struct type.
Example
package main
import (
"fmt"
)
type User struct {
username string
email string
password string
}
func (u *User) changePassword(newPassord string) {
u.password = newPassord
}
func (u User) getDetails() string {
return fmt.Sprintf("Username is %s\nEmail is %s\nPassword is %s", u.username, u.email, u.password)
}
func main() {
user := User{
username: "John Doe",
email: "johndoe@golinuxcloud.com",
password: "mysecretpassword",
}
fmt.Println(user.getDetails())
user.changePassword("supersecretpassword")
fmt.Println("New password: ", user.password)
}
func getNamePointer(name string) *string {
return &name
}
Output
Username is John Doe
Email is johndoe@golinuxcloud.com
Password is mysecretpassword
New password: supersecretpassword
Functions Versus Methods
Since you can use a method as a function, you might wonder when you should declare a function and when you should use a method.
The differentiator is whether or not your function depends on other data. A package-level state should be effectively immutable.
- Any time your logic depends on values that are configured at startup or changed while your program is running, those values should be stored in a struct and that logic should be implemented as a method.
- If your logic only depends on the input parameters, then it should be a function.
Types, packages, modules, testing, and dependency injection are interrelated concepts
Anonymous Functions
Not only can you assign functions to variables, you can also define new functions within a function and assign them to variables.
These inner functions are anonymous functions; they don’t have a name. You don’t have to assign them to a variable, either. You can write them inline and call them immediately.
You declare an anonymous function with the keyword func
immediately followed by the input parameters, the return values, and the opening brace. It is a compile-time error to try to put a function name between func and the input parameters.
Just like any other function, an anonymous golang function is called by using parenthesis.
Example
package main
import "fmt"
func main() {
func() {
fmt.Println("Hello world from GolinuxCloud")
}()
}
Output
Hello world from GolinuxCloud
Go closure function
Functions declared inside of functions are special; they are closures. This is a computer science word that means that functions declared inside of functions are able to access and modify variables declared in the outer function.
All of this inner function and closure stuff might not seem all that interesting at first. What benefit do you get from making mini-functions within a larger function? Why does Go have this feature?
One thing that closures allow you to do is limit a function’s scope. If a function is only going to be called from one other function, but it’s called multiple times, you can use an inner function to “hide” the called function. This reduces the number of declarations at the package level, which can make it easier to find an unused name.
Closures really become interesting when they are passed to other functions or returned from a function. They allow you to take the variables within your function and use those values outside of your function.
Passing Functions as Parameters
Since functions are values and you can specify the type of a function using its parameter and return types, you can pass functions as parameters into functions. If you aren’t used to treating functions like data, you might need a moment to think about the implications of creating a closure that references local variables and then passing that closure to another function. It’s a very useful pattern and appears several times in the standard library.
One example is sorting slices. There’s a function in the sort package in the standard library called sort.Slice
. It takes in any slice and a function that is used to sort the slice that’s passed in. Let’s see how it works by sorting a slice of a struct using two different fields.
package main
import (
"fmt"
"sort"
)
func main() {
type Person struct {
FirstName string
LastName string
Age int
}
people := []Person{
{"Amit", "Kumar", 42},
{"Ram", "Singh", 18},
{"Tulip", "Sharma", 51},
}
fmt.Println(people)
// sort by last name
sort.Slice(people, func(i int, j int) bool {
return people[i].LastName < people[j].LastName
})
fmt.Println(people)
}
Output
[{Amit Kumar 42} {Ram Singh 18} {Tulip Sharma 51}]
[{Amit Kumar 42} {Tulip Sharma 51} {Ram Singh 18}]
Explanation
The closure that’s passed to sort.Slice
has two parameters, i
and j
, but within the closure, we can refer to people so we can sort it by the LastName
field. In computer science terms, people is captured by the closure.
Returning Functions from Functions
Not only can you use a closure to pass some function state to another function, you can also return a closure from a function. Let’s show this off by writing a function that returns a multiplier function.
package main
import "fmt"
func makeMult(base int) func(int) int {
return func(factor int) int {
return base * factor
}
}
func main() {
twoBase := makeMult(2)
threeBase := makeMult(3)
for i := 0; i < 3; i++ {
fmt.Println(twoBase(i), threeBase(i))
}
}
Output
0 0
2 3
4 6
Recursive functions
A recursive function is a function that calls itself repeatedly until a certain condition (exit condition or base condition) is met.
Example
package main
import "fmt"
func main() {
fmt.Println("Countdown Starts")
countDown(0)
}
func countDown(num int) {
if num > 5 {
fmt.Println("Countdown Stops")
} else {
fmt.Println(num)
countDown(num + 1)
}
}
Output
Countdown Starts
0
1
2
3
4
5
Countdown Stops
In the above example the countDown()
function will call itself until the base condition if num > 5
if met.
Using Defer keyword
Programs often create temporary resources, like files or network connections, that need to be cleaned up. This cleanup has to happen, no matter how many exit points a function has, or whether a function completed successfully or not. In Go, the cleanup code is attached to the function with the defer
keyword.
Example
package main
import (
"fmt"
"os"
)
func main() {
defer func() {
msg := recover()
fmt.Println(msg)
}()
f, err := os.Create(".") // . is a current directory
if err != nil {
panic("failed to create file")
}
defer f.Close()
// no matter what happens here file will be closed
fmt.Fprintf(f, "hello world")
}
Output
failed to create file
Explanation
A deferred function call can handle panic by calling the recover built-in function. Each deferred call is put on stack, and executed in reverse order when the surrounding function ends. The reversed order helps deallocate resources correctly. The defer
statement must be reached for a function to be called.
Once we know we have a valid file handle, we need to close it after we use it, no matter how we exit the function. To ensure the cleanup code runs, we use the defer keyword, followed by a golang function or method call.
Summary
A good software is a software that has been modularized into small chunks of code that are responsible for a specific task. Functions make it easy to break code into small code statements that can be reused over and over in the program. In this article , we learn how to declare a function, name functions, pass data into functions, return values and different types of functions.