Using golang function like a PRO [Practical Examples]

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.

Advertisement

Example

Using golang function like a PRO [Practical Examples]

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

Advertisement
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.

 

Returning 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.

Using golang function like a PRO [Practical Examples]

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.

 

References

Go Functions

 

Didn't find what you were looking for? Perform a quick search across GoLinuxCloud

If my articles on GoLinuxCloud has helped you, kindly consider buying me a coffee as a token of appreciation.

Buy GoLinuxCloud a Coffee

For any other feedbacks or questions you can either use the comments section or contact me form.

Thank You for your support!!

Leave a Comment

X