A Tutorial for Learning Go Functions, Loops, and Errors
Updated by Linode Contributed by Mihalis Tsoukalos
Introduction
Go is a modern, open source, and general-purpose programming language that began as an internal Google project and was officially announced at the end of 2009. Go was inspired by many other programming languages including C, Pascal, Alef, and Oberon. Its spiritual fathers were Robert Griesemer, Ken Thomson, and Rob Pike, who all designed Go as a language for professional programmers that want to build reliable, robust, and efficient software. Apart from its syntax and its standard functions, Go comes with a rich standard library.
In this Guide
This guide will cover the following topics:
- A quick introduction on how to execute Go code
- How to use loops
- How to create functions
- How to handle errors
NoteThis guide was written with Go version 1.13.
Before You Begin
You will need Go installed on your computer. To get it, go to Go’s official download page and get the installer for your operating system, or you can install it from source. Follow the installation instructions for your operating system.
Add
/usr/local/go/binto thePATHenvironment variable:export PATH=$PATH:/usr/local/go/binYou may need to restart your shell for this change to apply.
The Advantages of Go
Although Go is not perfect, it has many advantages, including the following:
- It is a modern programming language that was made by experienced developers for developers.
- The code is easy to read.
- Go keeps concepts orthogonal, or simple, because a few orthogonal features work better than many overlapping ones.
- The compiler prints practical warnings and error messages that help you solve the actual problem.
- It has support for procedural, concurrent, and distributed programming.
- Go supports garbage collection so you do not have to deal with memory allocation and deallocation.
- Go can be used to build web applications and it provides a simple web server for testing purposes.
- The standard Go library offers many packages that simplify the work of the developer.
- It uses static linking by default, which means that the produced binary files can be easily transferred to other machines with the same OS and architecture. As a consequence, once a Go program is compiled successfully and the executable file is generated, the developer does not need to worry about dependencies and library versions.
- The code is portable, especially among UNIX machines.
- Go can be used for writing UNIX systems software.
- It supports Unicode by default which means that you do not need any extra code for printing characters from multiple human languages or symbols.
Executing Go code
There are two kinds of Go programs: autonomous programs that are executable, and Go libraries. Go does not care about an autonomous program’s file name. What matters is that the package name is main and that there is a single main() function in it. This is because the main() function is where program execution begins. As a result, you cannot have multiple main() functions in the files of a single project.
A Simple Go program
This is the Go version of the Hello World program:
- ./helloworld.go
-
1 2 3 4 5 6 7 8 9package main import ( "fmt" ) func main() { fmt.Println("Hello World!") }
All Go code is delivered within Go packages. For executable programs, the package name should be
main. Package declarations begin with thepackagekeyword.Executable programs should have a function named
main()without any function parameters. Function definitions begin with thefunckeyword.Go packages might include
importstatements for importing Go packages. However, Go demands that you use some functionality from each one of the packages that you import. There is a way to bypass this rule, however, it is considered a bad practice to do this.The
helloworld.gofile above imports thefmtpackage and uses thefmt.Println()function from that package.Note
All exported package functions begin with an uppercase letter. This follows the Go rule: if you export something outside the current package, it should begin with an uppercase letter. This rule applies even if the field of the Go structure or the global variable is included in a Go package.Go statements do not need to end with a semicolon. However, you are free to use semicolons if you wish. For more information on formatting with curly braces, see the section below.
Now that you better understand the
helloworld.goprogram, execute it with thego runcommand:go run helloworld.goYou will see the following output:
Hello World!This is the simplest of two ways that you can execute Go code. The
go runcommand compiles the code and creates a temporary executable file that is automatically executed and then it deletes that temporary executable file. This is similar to using a scripting programming language.The second method to execute Go code is to use the
buildcommand. Run the following command to use this method:go build helloworld.goThe result of that command is a binary executable file that you have to manually execute. This method is similar to the way you execute C code on a UNIX machine. The executable file is named after the Go source filename, which means that in this case the result will be an executable file named
helloworld. Go creates statically linked executable files that have no dependencies to external libraries.Execute the
helloworldfile:./helloworldYou will see the following output:
Hello World!Note
Thego runcommand is usually used while experimenting and developing new Go projects. However, if you need to transfer an executable file to another system with the same architecture, you should usego build.
Formatting Curly Braces
The following version of the “Hello World” program will not compile:
- ./curly.go
-
1 2 3 4 5 6 7 8 9 10package main import ( "fmt" ) func main() { fmt.Println("Hello World!") }
Execute the program above, and observer the error message generated by the compiler:
go run curly.go# command-line-arguments ./curly.go:7:6: missing function body ./curly.go:8:1: syntax error: unexpected semicolon or newline before {
This error message is generated because Go requires the use of semicolons as statement terminators in many contexts and the compiler automatically inserts the required semicolons when it thinks that they are necessary. Putting the opening curly brace (
{) on its own line makes the Go compiler look for a semicolon at the end of the previous line (func main()), which is the cause of the error message.There is only one way to format curly braces in Go; the opening curly brace must not appear on it’s own line. Additionally, you must use curly braces even if a code block contains a single Go statement, like in the body of a
forloop. You can see an example of this in the first version of thehelloworld.goprogram or in the Loops in Go section.
The Assignment Operator and Short Variable Declarations
- Go supports assignment (
=) operators and short variable declarations (:=). - With
:=you can declare a variable and assign a value to it at the same time. The type of the variable is inferred from the given value. You can use
=in two cases. First, to assign a new value to an existing variable and second, to declare a new variable, provided that you also give its type.For example,
var aVariable int = 10, is equivalent toaVariable := 10assumingaVariableis anint.When you specifically want to control a variable’s type, it is safer to declare the variable and its type using
varand then assign a value to it using=.
Loops in Go
The file loops.go demonstrates loops in Go:
- ./loops.go
-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48package main import ( "fmt" ) func main() { for loopIndex := 0; loopIndex < 20; loopIndex++ { if loopIndex%10 == 0 { continue } if loopIndex == 19 { break } fmt.Print(loopIndex, " ") } fmt.Println() // Use break to exit the for loop loopIndex := 10 for { if loopIndex < 0 { break } fmt.Print(loopIndex, " ") loopIndex-- } fmt.Println() // This is similar to a while(true) do something loop loopIndex = 0 anExpression := true for ok := true; ok; ok = anExpression { if loopIndex > 10 { anExpression = false } fmt.Print(loopIndex, " ") loopIndex++ } fmt.Println() anArray := [5]int{0, 1, -1, 2, -2} for loopIndex, value := range anArray { fmt.Println("index:", loopIndex, "value: ", value) } }
There are two types of
forloops in Go. Traditionalforloops that use a control variable initialization, condition, and afterthought; and those that iterate over the elements of a Go data type such as an array or a map using therangekeyword.Go has no direct support for
whileloops. If you want to use awhileloop, you can emulate it with aforloop.In their simplest form,
forloops allow you to iterate, a predefined number of times, for as long as a condition is valid, or according to a value that is calculated at the beginning of theforloop. Such values include the size of a slice or an array, or the number of keys on a map. However,rangeis more often used for accessing all the elements of a slice, an array, or a map because you do not need to know the object’s cardinality in order to process its elements one by one. For simplicity, this example uses an array, and a later example will use a slice.You can completely exit a
forloop using thebreakkeyword. Thebreakkeyword also allows you to create aforloop without an exit condition because the exit condition can be included in the code block of theforloop. You are also allowed to have multiple exit conditions in aforloop.You can skip a single iteration of a for loop using the
continuekeyword.
Execute the
loops.goprogram:go run loops.goYou will see the following output:
1 2 3 4 5 6 7 8 9 11 12 13 14 15 16 17 18 10 9 8 7 6 5 4 3 2 1 0 0 1 2 3 4 5 6 7 8 9 10 11 index: 0 value: 0 index: 1 value: 1 index: 2 value: -1 index: 3 value: 2 index: 4 value: -2
Functions in Go
Functions are first class citizens in Go, which means that functions can be parameters to other functions as well as returned by functions. This section will illustrate various types of functions.
Go also supports anonymous functions. These can be defined inline without the need for a name and they are usually used for implementing operations that require a small amount of code. In Go, a function can return an anonymous function or take an anonymous function as one of its arguments. Additionally, anonymous functions can be attached to Go variables. In functional programming terminology anonymous functions are called closures. It is considered a good practice for anonymous functions to have a small implementation and a local focus.
Regular functions
This section will present the implementation of some traditional functions.
- ./functions.go
-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58package main import ( "fmt" ) func doubleSquare(firstNum int) (int, int) { return firstNum * 2, firstNum * firstNum } func namedMinMax(firstNum, secondNum int) (min, max int) { if firstNum > secondNum { min = secondNum max = firstNum } else { min = firstNum max = secondNum } return } func minMax(firstNum, secondNum int) (min, max int) { if firstNum > secondNum { min = secondNum max = firstNum } else { min = firstNum max = secondNum } return min, max } func main() { secondNum := 10 square := func(numberToSquare int) int { return numberToSquare * numberToSquare } fmt.Println("The square of", secondNum, "is", square(secondNum)) double := func(numberToDouble int) int { return numberToDouble + numberToDouble } fmt.Println("The double of", secondNum, "is", double(secondNum)) fmt.Println(doubleSquare(secondNum)) doubledNumber, squaredNumber := doubleSquare(secondNum) fmt.Println(doubledNumber, squaredNumber) value1 := -10 value2 := -1 fmt.Println(minMax(value1, value2)) min, max := minMax(value1, value2) fmt.Println(min, max) fmt.Println(namedMinMax(value1, value2)) min, max = namedMinMax(value1, value2) fmt.Println(min, max) }
The
main()function takes no arguments and returns no arguments. Once the special functionmain()exits, the program automatically ends.The
doubleSquare()function requires a singleintparameter and returns twointvalues, which is defined as(int, int).All function arguments must have a name – variadic functions are the only exception to this rule.
If a function returns a single value, you do not need to put parenthesis around its type.
Because
namedMinMax()has named return values in its signature, theminandmaxparameters are automatically returned in the order in which they were put in the function definition. Therefore, the function does not need to explicitly return any variables or values in its return statement at the end, and does not.minMax()function has the same functionality asnamedMinMax()but it explicitly returns its values demonstrating that both ways are valid.Both
squareanddoublevariables inmain()are assigned an anonymous function. However, nothing stops you from changing the value ofsquare,double, or any other variable that holds the result of an anonymous function, afterwards. This means that both variables may have a different value in the future.
Execute the
functions.goprogram.go run functions.goYour output will resemble the following:
The square of 10 is 100 The double of 10 is 20 20 100 20 100 -10 -1 -10 -1 -10 -1 -10 -1
Variadic functions
Variadic functions are functions that accept a variable number of arguments. The most popular variadic functions in Go can be found in the fmt package. The code of variadic.go illustrates the creation and the use of variadic functions.
- ./variadic.go
-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31package main import ( "fmt" ) func varFunc(input ...string) { fmt.Println(input) } func oneByOne(message string, sliceOfNumbers ...int) int { fmt.Println(message) sum := 0 for indexInSlice, sliceElement := range sliceOfNumbers { fmt.Print(indexInSlice, sliceElement, "\t") sum = sum + sliceElement } fmt.Println() sliceOfNumbers[0] = -1000 return sum } func main() { many := []string{"12", "3", "b"} varFunc(many...) sum := oneByOne("Adding numbers...", 1, 2, 3, 4, 5, -1, 10) fmt.Println("Sum:", sum) sliceOfNumbers := []int{1, 2, 3} sum = oneByOne("Adding numbers...", sliceOfNumbers...) fmt.Println(sliceOfNumbers) }
The
...operator used as a prefix to a type like...intis called the pack operator, whereas the unpack operator appends a slice likesliceOfNumbers.... A slice is a Go data type that is essentially an abstraction of an array of unspecified length.Each variadic function can use the pack operator once. The
oneByOne()function accepts a singlestringand a variable number of integer arguments using thesliceOfNumbersslice.The
varFuncfunction accepts a single argument and just calls thefmt.Println()function.Another note about slices: the second call to
oneByOne()is using a slice. Any changes you make to that slice inside the variadic function will persist after the function exits because this is how slices work in Go.
Execute the
variadic.goprogram:go run variadic.goThe output will resemble the following
[12 3 b] Adding numbers... 0 1 1 2 2 3 3 4 4 5 5 -1 6 10 Sum: 24 Adding numbers... 0 1 1 2 2 3 [-1000 2 3]
Functions and pointer variables
Go supports pointers and this section will briefly present how functions can work with pointers. A future Go guide will talk about pointers in more detail, but here is a brief overview.
- ./fPointers.go
-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25package main import ( "fmt" ) func getPointer(varToPointer *float64) float64 { return *varToPointer * *varToPointer } func returnPointer(testValue int) *int { squareTheTestValue := testValue * testValue return &squareTheTestValue } func main() { testValue := -12.12 fmt.Println(getPointer(&testValue)) testValue = -12 fmt.Println(getPointer(&testValue)) theSquare := returnPointer(10) fmt.Println("sq value:", *theSquare) fmt.Println("sq memory address:", theSquare) }
- The
getPointer()function takes a pointer argument to afloat64, which is defined asvarToPointer *float64, wherereturnPointer()returns a pointer to anint, which is declared as*int.
Execute the
fPointers.goprogram:go run fPointers.goThe output will resemble the following:
146.8944 144 sq value: 100 sq memory address: 0xc00001a0b8
Functions with Functions as Parameters
Go functions can have functions as parameters.
- ./fArgF.go
-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21package main import "fmt" func doubleIt(numToDouble int) int { return numToDouble + numToDouble } func squareIt(numToSquare int) int { return numToSquare * numToSquare } func funFun(functionName func(int) int, variableName int) int { return functionName(variableName) } func main() { fmt.Println("funFun Double:", funFun(doubleIt, 12)) fmt.Println("funFun Square:", funFun(squareIt, 12)) fmt.Println("Inline", funFun(func(numToCube int) int { return numToCube * numToCube * numToCube }, 12)) }
The
funFun()function accepts two parameters, a function parameter namedfunctionNameand anintvalue. ThefunctionNameparameter should be a function that takes oneintargument and returns anintvalue.The first
fmt.Println()call inmain()usesfunFun()and passes thedoubleItfunction, without any parentheses, as its first parameter.The second
fmt.Println()call usesfunFun()withsquareItas its first parameter.In the last
fmt.Println()statement the implementation of the function parameter is defined inside the call tofunFun()using an anonymous function.
Execute the
fArgF.goprogram:go run fArgF.goThe output will resemble the following:
function1: 24 function2: 144 Inline 1728
Functions Returning Functions
Go functions can return functions.
- ./fRetF.go
-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23package main import ( "fmt" ) func squareFunction() func() int { numToSquare := 0 return func() int { numToSquare++ return numToSquare * numToSquare } } func main() { square1 := squareFunction() square2 := squareFunction() fmt.Println("First Call to square1:", square1()) fmt.Println("Second Call to square1:", square1()) fmt.Println("First Call to square2:", square2()) fmt.Println("Third Call to square1:", square1()) }
squareFunction()returns an anonymous function with thefunc() intsignature.As
squareFunction()is called two times, you will need to use two separate variables,square1andsquare2to keep the two return values.
Execute the
fRetF.goprogram:go run fRetF.goYour output will resemble the following:
First Call to square1: 1 Second Call to square1: 4 First Call to square2: 1 Third Call to square1: 9Notice that the values of
square1andsquare2are not connected even though they both came fromsquareFunction().
Errors in Go
Errors and error handling are two important topics in Go. Go puts so much importance on error messages that it has a dedicated data type for errors, aptly named error. This also means that you can easily create your own error messages if you find that what Go gives you is not adequate. You will most likely need to create and handle your own errors when you are developing your own Go packages.
Recognizing an error condition is one task, while deciding how to react to an error condition is another task. Therefore, some error conditions might require that you immediately stop the execution of the program, whereas in other error situations, you might just print a warning message and continue.
- ./errors.go
-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29package main import ( "errors" "fmt" "strconv" ) func main() { customError := errors.New("My Custom Error!") if customError.Error() == "My Custom Error!" { fmt.Println("!!") } stringToConvert1 := "123" stringToConvert2 := "43W" _, err := strconv.Atoi(stringToConvert1) if err != nil { fmt.Println(err) return } _, err = strconv.Atoi(stringToConvert2) if err != nil { fmt.Println(err) return } }
The
strconv.Atoi()function tries to convert a string into an integer, provided that the string is a valid integer, and returns two things, an integer value and anerrorvariable. If theerrorvariable isnil, then the conversion was successful and you get a valid integer. The_character tells Go to ignore one, as in this case, or more of the return values of a function.Most of the time, you need to check whether an error variable is equal to
niland then act accordingly. This kind of Go code is very popular in Go programs and you will see it and use it multiple times.Also presented here is the
errors.New()function that allows you to create a custom error message anderrors.Error()function that allows you to convert anerrorvariable into astringvariable.
Execute the
errors.goprogram:go run errors.goYour output will resemble the following:
!! strconv.Atoi: parsing "43W": invalid syntax
Summary
In this guide you learned the basics about the Go programming language, how to execute programs, how to write loops, how to handle errors, and you saw examples for various function types.
More Information
You may wish to consult the following resources for additional information on this topic. While these are provided in the hope that they will be useful, please note that we cannot vouch for the accuracy or timeliness of externally hosted materials.
Join our Community
Find answers, ask questions, and help others.
This guide is published under a CC BY-ND 4.0 license.