essential-go
essential-go
Go Programming
First Edition
Baiju Muthukadan
Essentials of Go Programming
First Edition
(English)
Baiju Muthukadan
©2015-2023 Baiju Muthukadan <baiju.m.mail@gmail.com>
ii
To my mother Sulochana M.I.
iv
Contents
Preface ix
Acknowledgements xi
1 Introduction 1
1.1 Preparations . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.2 Organization of Chapters . . . . . . . . . . . . . . . . . 5
1.3 Suggestions to Use this Book . . . . . . . . . . . . . . . 7
2 Quick Start 9
2.1 Hello World! . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.2 Building and Running Programs . . . . . . . . . . . . . 10
2.3 The Example Explained . . . . . . . . . . . . . . . . . . 11
2.4 Organizing Code . . . . . . . . . . . . . . . . . . . . . . . 12
2.5 Basics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.6 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
3 Control Structures 29
3.1 If . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
3.2 For . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
3.3 Switch Cases . . . . . . . . . . . . . . . . . . . . . . . . . 39
3.4 Defer Statements . . . . . . . . . . . . . . . . . . . . . . 43
3.5 Deffered Panic Recover . . . . . . . . . . . . . . . . . . 44
3.6 Goto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
3.7 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
v
vi CONTENTS
4 Data Structures 49
4.1 Primitive Data Types . . . . . . . . . . . . . . . . . . . . 49
4.2 Constants . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
4.3 Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
4.4 Slices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
4.5 Maps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
4.6 Custom Data Types . . . . . . . . . . . . . . . . . . . . . 66
4.7 Pointers . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
4.8 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
5 Functions 73
5.1 Parameters . . . . . . . . . . . . . . . . . . . . . . . . . . 75
5.2 Return Values . . . . . . . . . . . . . . . . . . . . . . . . 76
5.3 Variadic Functions . . . . . . . . . . . . . . . . . . . . . 77
5.4 Anonymous Functions . . . . . . . . . . . . . . . . . . . 78
5.5 Function as Value . . . . . . . . . . . . . . . . . . . . . . 79
5.6 Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
5.7 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
6 Objects 85
6.1 Type with Multiple Interfaces . . . . . . . . . . . . . . . 87
6.2 Empty Interface . . . . . . . . . . . . . . . . . . . . . . . 87
6.3 Pointer Receiver . . . . . . . . . . . . . . . . . . . . . . . 88
6.4 Type Assertions . . . . . . . . . . . . . . . . . . . . . . . 89
6.5 Type Switches . . . . . . . . . . . . . . . . . . . . . . . . 90
6.6 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
7 Concurrency 93
7.1 Goroutine . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
7.2 Channels . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
7.3 Waitgroups . . . . . . . . . . . . . . . . . . . . . . . . . . 96
7.4 Select . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
7.5 Buffered Channels . . . . . . . . . . . . . . . . . . . . . 98
7.6 Channel Direction . . . . . . . . . . . . . . . . . . . . . . 99
CONTENTS vii
8 Packages 103
8.1 Creating a Package . . . . . . . . . . . . . . . . . . . . . 104
8.2 Package Initialization . . . . . . . . . . . . . . . . . . . . 105
8.3 Documenting Packages . . . . . . . . . . . . . . . . . . . 106
8.4 Publishing Packages . . . . . . . . . . . . . . . . . . . . 107
8.5 Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
8.6 Moving Type Across Packages . . . . . . . . . . . . . . 109
8.7 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
9 Input/Output 113
9.1 Command Line Arguments . . . . . . . . . . . . . . . . 113
9.2 Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
9.3 Standard Streams . . . . . . . . . . . . . . . . . . . . . . 116
9.4 Using flag Package . . . . . . . . . . . . . . . . . . . . . 118
9.5 String Formatting . . . . . . . . . . . . . . . . . . . . . . 119
9.6 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
10 Testing 125
10.1 Failing a Test . . . . . . . . . . . . . . . . . . . . . . . . . 127
10.2 Logging Message . . . . . . . . . . . . . . . . . . . . . . 127
10.3 Failing with Log Message . . . . . . . . . . . . . . . . . 128
10.4 Skipping Test . . . . . . . . . . . . . . . . . . . . . . . . . 129
10.5 Parallel Running . . . . . . . . . . . . . . . . . . . . . . . 130
10.6 Sub Tests . . . . . . . . . . . . . . . . . . . . . . . . . . . 130
10.7 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
11 Tooling 133
11.1 Getting Help . . . . . . . . . . . . . . . . . . . . . . . . . 133
11.2 Basic Information . . . . . . . . . . . . . . . . . . . . . . 134
11.3 Building and Running . . . . . . . . . . . . . . . . . . . 136
11.4 Running Test . . . . . . . . . . . . . . . . . . . . . . . . . 137
viii CONTENTS
Index 157
Preface
ix
an enjoyable activity, although at times it can become demanding.
Since this book is self-published, I had the freedom to take my time
and ensure its quality.
Throughout the years, I have conducted numerous Go workshops
in various parts of India. During these workshops, I always desired
to offer something more to the participants, and I believe this book
will fulfill that aspiration.
Software development encompasses more than just programming
languages. It involves acquiring additional skills such as
proficiently using text editors/IDEs, version control systems, and
your preferred operating system. Furthermore, it is beneficial
to continually expand your knowledge by exploring different
languages and technologies throughout your career. While Go
leans toward an object-oriented programming style, you may also
find it worthwhile to explore functional programming languages
like Scheme and Haskell.
Apart from technical skills, business domain knowledge and soft
skills play a pivotal role in your career growth. However, discussing
these aspects in detail falls beyond the scope of this context. With
that said, I conclude by wishing you a successful career.
Baiju Muthukadan
Kozhikode, Kerala, India
May 2023
x
Acknowledgements
xi
xii
Chapter 1
Introduction
1
2 1.1. Preparations
1.1 Preparations
You can use any text editor to write code. If you are not familiar
with any text editor, consider using Vim. You can bootstrap Vim
configuration for Go programming language from this website:
https://vim-bootstrap.com.
Using a source code management system like Git would be helpful.
Keeping all your code under version control is highly recommended.
You can use a public code hosting service like GitHub, Bitbucket,
and GitLab to store your examples.
Linux Installation
The first line ensure that current working directory is the /tmp
directory.
In the second line, replace the version number from the Go
downloads website. It’s going to download the 64 bit binary for
GNU/Linux. The wget is a command line download manager.
Alternatively you can use curl or any other download manager to
download the tar ball.
The third line extract the downloaded tar ball into /usr/local/go
directory.
Now you can exit from the root user or stop using sudo.
By default Go packages are installed under $HOME/go directory.
This directory can be overridden using GOPATH environment
variable. Any binaries installed using go install goes into
$GOPATH/bin directory.
You can also update PATH environment variable to include new
binary locations. Open the $HOME/.bashrc file in a text editor and
enter this lines at the bottom.
4 1.1. Preparations
export PATH=$HOME/go/bin:/usr/local/go/bin:$PATH
Windows Installation
There are separate installers (MSI files) available for 32 bit and 64
bit versions of Windows. The 32 bit version MSI file will be named
like this: go1.x.y.windows-386.msi (Replace x.y with the current
version). Similarly for 64 bit version, the MSI file will be named
like this: go1.x.y.windows-amd64.msi (Replace x.y with the current
version).
You can download the installers (MSI files) from here: https://go.
dev/dl
After downloading the installer file, you can open the MSI file by
double clicking on that file. This should prompts few things about
the installation of the Go compiler. The installer place the Go
related files in the C:\Go directory.
The installer also put the C:\Go\bin directory in the system PATH
environment variable. You may need to restart any open command
prompts for the change to take effect.
You also need to create a directory to download third party
packages from github.com or similar sites. The directory can be
created at C:\mygo like this:
C:\> mkdir C:\mygo
After this you can set GOPATH environment variable to point to this
location.
C:\> go env -w GOPATH=C:\mygo
Hello World!
1 package main
3 import "fmt"
5 func main() {
6 fmt.Println("Hello, World!")
7 }
Once you saved the above source code into a file. You can open
your command line program (bash or cmd.exe) then change to the
directory where you saved the program code and run the above
program like this:
$ go run hello.go
Hello, World!
Using Git
Chapter 9: Input/Output
This chapter discussed about various input/output related
functionalities in Go. The chapter explained using command
line arguments and interactive input. The chaptered using
flag package. It also explained about various string formatting
techniques.
Summary
Quick Start
This chapter walks through a few basic topics in Go. You should be
able to write simple programs using Go after reading and practicing
the examples given in this chapter. The next 3 sections revisit the
hello world program introduced in the last chapter. Later we will
move on to a few basic topics in Go. We will learn about data
types, variables, comments, For loops, range clauses, If, functions,
operators, slices, and maps.
1 package main
3 import "fmt"
5 func main() {
6 fmt.Println("Hello, World!")
7 }
You can open your command line program and run the above
program like this:
9
10 2.2. Building and Running Programs
$ go run hello.go
Hello, World!
You can use the go build command to compile the source and
create executable binary programs. Later this executable can run
directly or copied to other similar systems and run.
To compile (build) the hello world program, you can use this
command:
$ go build hello.go
In Windows, the executable file name ends with .exe. This is how
you can run the executable in Windows:
C:\> hello.exe
Hello, World!
The first line is a clause defining the name of the package to which
the file belongs. In the above hello world program, the hello.go
file belongs to the the main package because of this clause at the
beginning of the file:
package main
The name of the package for the built-in packages will be the name
given within quotes of the import statement. If the import string is
a path separated by slash, then name of the package will be the last
part of the string. For example, ”net/http” package name is http.
For other third party vendor packages, the name should be verified
within the source code.
Names within the imported package can be referred using a dot
operator as you can see above: fmt.Println. A name is considered
as exported if it begins with a capital letter. For example, the name
Area is an exported name, but area is not exported.
The https://go.dev/play site can be used to share Go source code
publicly. You can also run the programs in the playground.
12 2.4. Organizing Code
Again we have added one blank line after the import statement for
readability. The fifth line starts with a function definition. In this
case, this is a special function named main. A function is a collection
of instructions or more specifically statements. A function
definition starts with func keyword followed by function name then
arguments (parameters) for the function within parenthesis and
finally statements within curly brackets. The main function is a
special function which doesn’t accept any arguments. The starting
curly bracket should be in the same line where function definition
started and statements should start in the next line. There should
be only one main function for an executable program.
Inside the main function, we are calling the Println function
available inside the fmt package.
fmt.Println("Hello, World!")
2.5 Basics
Data Types
Apart from string, and bool, Go has some other data types like int,
byte, float64 etc.
14 2.5. Basics
Variables
Let’s go back to the hello world example, if you want to print the
hello world message three times. You will be required to write that
sentence three times as given below.
1 package main
3 import "fmt"
5 func main() {
6 fmt.Println("Hello, World!")
7 fmt.Println("Hello, World!")
8 fmt.Println("Hello, World!")
9 }
1 package main
3 import "fmt"
5 func main() {
6 hw := "Hello, World!"
7 fmt.Println(hw)
8 fmt.Println(hw)
9 fmt.Println(hw)
10 }
As you can see in the above example, we are using two special
characters (:=) in between the variable name and the string literal.
The colon character immediately followed by equal character is
what you can use to define a short variable declaration in Go.
However, there is a small catch here, the this short syntax for
declaring variable will only work inside a function definition. The
Go compiler identify the type of variable as string. This process of
identifying data type automatically is called type inference.
To assign a new value to the variable, you can use = as given in the
below example:
Chapter 2. Quick Start 15
1 package main
3 import "fmt"
5 func main() {
6 hw := "Hello, World!"
7 fmt.Println(hw)
8 hw = "Hi, New World!"
9 fmt.Println(hw)
10 }
You can also explicitly define the type of variable instead of using
the := syntax. To define the type of a variable, you can use the
keyword var followed by the name of the type. Later, to assign a
string value for the hw variable, you can use = symbol instead of :=.
So, the example we can rewrite like this.
1 package main
3 import "fmt"
5 func main() {
6 var hw string
7 hw = "Hello, World!"
8 fmt.Println(hw)
9 fmt.Println(hw)
10 fmt.Println(hw)
11 }
Comments
The multi-line comment starts with /* and ends with */. And
everything in between is considered as comments.
Here is a multi-line comment to document the package named plus.
As you can see here, the comment is used to give a brief description
about the package and two example usages are also given.
1 /*
2 Package plus provides utilities for Google+
3 Sign-In (server-side apps)
5 Examples:
In the above example the first line is a line comment. The “godoc”
and similar tool treated this comment as an API documentation.
There is another comment in the line where name equality with
empty string is checked. These kind of comment helps the reader
of source code to understand what that attribute is used for.
18 2.5. Basics
For Loop
1 package main
3 import "fmt"
5 func main() {
6 hw := "Hello, World!"
7 for i := 0; i < 3; i++ {
8 fmt.Println(hw)
9 }
10 }
1 package main
3 import "fmt"
5 func main() {
6 sum := 0
Chapter 2. Quick Start 19
The initialization and increment part are optional as you can see
below.
1 package main
3 import "fmt"
5 func main() {
6 sum := 1
7 for sum < 1000 {
8 sum += sum
9 }
10 fmt.Println(sum)
11 }
1 package main
3 func main() {
4 for {
5 }
6 }
If
1 package main
20 2.5. Basics
3 import "fmt"
5 func main() {
6 money := 10000
7 fmt.Println("I am going to buy a bike.")
8 if money > 15000 {
9 fmt.Println("I am also going to buy a car.")
10 }
11 }
You can save the above program in a file named buy.go and run it
using go run. It’s going to print like this:
$ go run buy.go
I am going to buy a bike.
As you can see, the print statement in the line number 9 didn’t print.
Because that statement is within a condition block. The condition is
money > 15000, which is not correct. You can change the program
and alter the money value in line number 7 to an amount higher than
15000. Now you can run the program again and see the output.
Now let’s consider another scenario where you either want to buy
a bike or car but not both. The else block associated with if
condition will be useful for this.
Listing 2.12 : If with else block
1 package main
3 import "fmt"
5 func main() {
6 money := 20000
7 if money > 15000 {
8 fmt.Println("I am going to buy a car.")
9 } else {
10 fmt.Println("I am going to buy a bike.")
11 }
12 }
You can save the above program in a file named buy2.go and run it
using go run. It’s going to print like this:
$ go run buy2.go
I am going to buy a car.
1 package main
3 import "fmt"
5 func main() {
6 if money := 20000; money > 15000 {
7 fmt.Println("I am going to buy a car.")
8 } else {
9 fmt.Println("I am going to buy a bike.")
10 }
11 }
Function
2
f(r) = 3.14r
Figure 2.1: Mathematical function for area of a circle
This function square the input value and multiply with 3.14.
Depending on the input value the output varies.
f(r)
r 2 y
3.14r
As you can see in the above diagram, r is the input and y is the
output. A function in Go can take input arguments and perform
actions and return values. A simple implementation of this function
in Go looks like this.
func Area(r float64) float64 {
return 3.14 * r * r
22 2.5. Basics
1 package main
3 import "fmt"
10 func main() {
11 area := Area(5.0)
12 fmt.Println(area)
13 }
Operators
Apart from the conditional AND, there are conditional OR (||) and
NOT (!) logical operators. We will see more about operators in the
next chapter.
Slices
In the above example, the length of slice is 3 and the slice values
are string data. The len function gives the length of slice. See this
complete example:
1 package main
3 import "fmt"
5 func main() {
6 colors := []string{"Red", "Green", "Blue"}
7 fmt.Println("Len:", len(colors))
8 for i, v := range colors {
9 fmt.Println(i, v)
10 }
11 }
If you save the above program in a file named colors.go and run
it, you will get output like this:
24 2.5. Basics
$ go run colors.go
Len: 3
0 Red
1 Green
2 Blue
1 package main
3 import "fmt"
5 func main() {
6 colors := []string{"Red", "Green", "Blue"}
7 fmt.Println("Len:", len(colors))
8 for _, v := range colors {
9 fmt.Println(v)
10 }
11 }
If you just want to get the index without value, you can use just use
one variable to the left of range clause as give below.
Listing 2.17 : Range loop without index
1 package main
3 import "fmt"
5 func main() {
6 colors := []string{"Red", "Green", "Blue"}
7 fmt.Println("Len:", len(colors))
8 for i := range colors {
9 fmt.Println(i, colors[i])
10 }
11 }
In the above example, we are accessing the value using the index
syntax: colors[i].
Chapter 2. Quick Start 25
Maps
If the key doesn’t exist, a zero value will be returned. For example,
in the below example, value of pineappleCount is going be 0.
pineappleCount := fruits["Pineapple"]
2.6 Exercises
import "fmt"
26 2.6. Exercises
func main() {
for i := 1; i < 10; i++ {
if i%2 == 0 {
fmt.Println(i * 5)
}
}
}
import "fmt"
func main() {
hw := "Hello, World!"
rhw := Reverse(hw)
fmt.Println(rhw)
}
import "fmt"
func main() {
sum := 0
for i := 1; i < 50; i++ {
if i%2 == 0 {
sum = sum + i
} else {
if i%3 == 0 {
sum = sum + i
}
Chapter 2. Quick Start 27
}
}
fmt.Println("Sum:", sum)
}
import "fmt"
func main() {
sum := 0
for i := 1; i < 50; i++ {
if i%2 == 0 || i%3 == 0 {
sum = sum + i
}
}
fmt.Println("Sum:", sum)
}
Additional Exercises
Summary
Control Structures
Control structure determines how the code block will get executed
for the given conditional expressions and parameters. Go provides
a number of control structures including for, if, switch, defer,
and goto. The Quick Start chapter has already introduced control
structures like if and for. This chapter will elaborate more about
these topics and introduce some other related topics.
3.1 If
Basic If
1 package main
29
30 3.1. If
3 import "fmt"
5 func main() {
6 if 1 < 2 {
7 fmt.Println("1 is less than 2")
8 }
9 }
If and Else
1 package main
3 import "fmt"
5 func main() {
6 if 3 > 4 {
7 fmt.Println("3 is greater than 4")
8 } else {
9 fmt.Println("3 is not greater than 4")
10 }
11 }
In the above code, the code will be evaluated as false. So, the
statements within else block will get executed.
Example
1 package main
3 import (
4 "fmt"
5 "os"
6 "strconv"
7 )
9 func main() {
10 i := os.Args[1]
11 n, err := strconv.Atoi(i)
12 if err != nil {
13 fmt.Println("Not a number:", i)
14 os.Exit(1)
15 }
16 if n%2 == 0 {
17 fmt.Printf("%d is even\n", n)
18 } else {
19 fmt.Printf("%d is odd\n", n)
20 }
21 }
The os package has an attribute named Args. The value of Args will
be a slice of strings which contains all command line arguments
passed while running the program. As we have learned from the
Quick Start chapter, the values can be accessed using the index
syntax. The value at zero index will be the program name itself and
the value at 1st index the first argument and the value at 2nd index
the second argument and so on. Since we are expecting only one
argument, you can access it using the 1st index (os.Args[1]).
The strconv package provides a function named Atoi to convert
strings to integers. This function return two values, the first one is
the integer value and the second one is the error value. If there is no
error during convertion, the error value will be nil. If it’s a non-nil
value, that indicates there is an error occured during conversion.
The nil is an identifier in Go which represents the “zero value” for
certain built-in types. The nil is used as the zero for these types:
interfaces, functions, pointers, maps, slices, and channels.
In the above example, the second expected value is an object
conforming to built-in error interface. We will discuss more about
errors and interfaces in later chapters. The zero value for interface,
that is nil is considered as there is no error.
The Exit function within os package helps to exit the program
prematurely. The argument passed will be exit status code.
32 3.1. If
Else If
1 package main
3 import "fmt"
5 func main() {
6 age := 10
7 if age < 10 {
8 fmt.Println("Junior", age)
9 } else if age < 20 {
10 fmt.Println("Senior", age)
11 } else {
12 fmt.Println("Other", age)
13 }
14 }
Inline Statement
In the previous section, the variable age was only within the If, Else
If and Else blocks. And that variable was not used used afterwards
in the function. Go provides a syntax to define a variable along with
the If where the scope of that variable will be within the blocks. In
Chapter 3. Control Structures 33
fact, the syntax can be used for any valid Go statement. However,
it is mostly used for declaring variables.
Here is an example where a variable named money is declared along
with the If control structure.
1 package main
3 import "fmt"
5 func main() {
6 if money := 20000; money > 15000 {
7 fmt.Println("I am going to buy a car.", money)
8 } else {
9 fmt.Println("I am going to buy a bike.", money)
10 }
11 // can't use the variable `money` here
12 }
3.2 For
Basic For
As we have seen briefly in the Quick Start, the For control structure
helps to create loops to repeat certain actions. The For control
structure has few syntax variants.
Consider a program to print few names.
1 package main
3 import "fmt"
5 func main() {
34 3.2. For
You can save the above program in a file named names.go and run
it like this:
$ go run name.go
Tom
Polly
Huck
Becky
1 package main
3 import "fmt"
5 func main() {
6 names := []string{"Tom", "Polly", "Huck", "Becky"}
7 for i := 0; i < len(names); i++ {
8 fmt.Println(names[i])
9 if names[i] == "Polly" {
Chapter 3. Control Structures 35
10 break
11 }
12 }
13 }
As you can see in the above code, the break statement can stand
alone without any other input. There is alternate syntax with label
similar to how goto works, which we are going to see below. This is
useful when you have multiple loops and want to break a particular
one, may be the outer loop.
1 package main
3 import "fmt"
5 func main() {
6 names := []string{"Tom", "Polly", "Huck", "Becky"}
7 Outer:
8 for i := 0; i < len(names); i++ {
9 for j := 0; j < len(names[i]); j++ {
10 if names[i][j] == 'u' {
11 break Outer
12 }
13 }
14 fmt.Println(names[i])
15 }
16 }
1 package main
3 import "fmt"
5 func main() {
6 names := []string{"Tom", "Polly", "Huck", "Becky"}
7 for i := 0; i < len(names); i++ {
8 if names[i] == "Polly" {
9 continue
10 }
11 fmt.Println(names[i])
12 }
13 }
1 package main
3 import "fmt"
5 func main() {
6 names := []string{"Tom", "Polly", "Huck", "Becky"}
7 Outer:
8 for i := 0; i < len(names); i++ {
9 for j := 0; j < len(names[i]); j++ {
10 if names[i][j] == 'u' {
11 continue Outer
12 }
13 }
14 fmt.Println(names[i])
15 }
16 }
Chapter 3. Control Structures 37
In the above code, just before the first loop a label is declared. Later
inside the inner loop to iterate through the name string and check
for the presence of character u. If the character u is found, then it
will continue the outer loop. If the label Outer is not used in the
continue statement, then the inner loop will be proceed to execute.
The statement for value initialization and the last pat to increment
value can be removed from the For control structure. The value
initialization can be moved outside For and value increment can be
moved inside loop.
The previous example can be changed like this:
1 package main
3 import "fmt"
5 func main() {
6 names := []string{"Tom", "Polly", "Huck", "Becky"}
7 i := 0
8 for i < len(names) {
9 i++
10 fmt.Println(names[i])
11 }
12 }
Infinite Loop
For loop has yet another syntax variant to support infinite loop.
You can create a loop that never ends until explicitly stopped using
break or exiting the whole program. To create an infinite loop, you
can use the for keyword followed by the curly bracket.
If any variable initialization is required, that should be declared
outside the loop. Conditions can be added inside the loop.
The previous example can be changed like this:
38 3.2. For
1 package main
3 import "fmt"
5 func main() {
6 names := []string{"Tom", "Polly", "Huck", "Becky"}
7 i := 0
8 for {
9 if i >= len(names) {
10 break
11 }
12 fmt.Println(names[i])
13 i++
14 }
15 }
Range Loops
The range clause form of the for loop iterates over a slice or map.
When looping over a slice using range, two values are returned for
each iteration. The first is the index, and the second is a copy of
the element at that index.
The previous example for loop can be simplified using the range
clause like this:
1 package main
3 import "fmt"
5 func main() {
6 characters := []string{"Tom", "Polly", "Huck", "Becky"}
7 for _, j := range characters {
8 fmt.Println(j)
9 }
10 }
1 package main
3 import "fmt"
5 func main() {
6 var characters = map[string]int{
7 "Tom": 8,
8 "Polly": 51,
9 "Huck": 9,
10 "Becky": 8,
11 }
12 for name, age := range characters {
13 fmt.Println(name, age)
14 }
15 }
Basic Switch
1 package main
3 import "fmt"
5 func main() {
6 v := 1
7 switch v {
8 case 0:
9 fmt.Println("zero")
10 case 1:
11 fmt.Println("one")
12 case 2:
13 fmt.Println("two")
14 default:
15 fmt.Println("unknown")
16 }
17 }
40 3.3. Switch Cases
If you change the value of v to 0, it’s going to print zero and for 2
it will print two. If the value is any number other than 0, 1 or 2, it’s
going to print unknown.
Fallthrough
1 package main
3 import "fmt"
5 func main() {
6 v := 1
7 switch v {
8 case 0:
9 fmt.Println("zero")
10 case 1:
11 fmt.Println("one")
12 fallthrough
13 case 2:
14 fmt.Println("two")
15 default:
16 fmt.Println("unknown")
17 }
18 }
Break
As you can see from the above examples, the switch statements
break implicitly at the end of each cases. The fallthrough
statement can be used to passdown control to the next case.
However, sometimes execution should be stopped early without
executing all statements. This can can be achieved using break
statements.
Here is an example:
1 package main
3 import (
4 "fmt"
5 "time"
6 )
8 func main() {
9 v := "Becky"
10 t := time.Now()
11 switch v {
12 case "Huck":
13 if t.Hour() < 12 {
14 fmt.Println("Good morning,", v)
15 break
16 }
17 fmt.Println("Hello,", v)
18 case "Becky":
19 if t.Hour() < 12 {
20 fmt.Println("Good morning,", v)
21 break
22 }
23 fmt.Println("Hello,", v)
24 default:
25 fmt.Println("Hello")
26 }
27 }
Multiple Cases
1 package main
3 import "fmt"
5 func main() {
6 o := "=="
7 switch o {
8 case "+", "-", "*", "/", "%", "&", "|", "^", "&^", "<<", ">>":
9 fmt.Println("Arithmetic operator")
10 case "==", "!=", "<", "<=", ">", ">=":
11 fmt.Println("Comparison operators")
12 case "&&", "||", "!":
13 fmt.Println("Logical operators")
14 default:
15 fmt.Println("Unknown operator")
16 }
17 }
In this example, if any of the value is matched in the given list, that
case will be executed.
Without Expression
1 package main
3 import "fmt"
5 func main() {
6 age := 10
7 switch {
8 case age < 10:
9 fmt.Println("Junior", age)
10 case age < 20:
11 fmt.Println("Senior", age)
12 default:
13 fmt.Println("Other", age)
14 }
15 }
Chapter 3. Control Structures 43
1 package main
3 import (
4 "fmt"
5 "time"
6 )
8 func main() {
9 defer fmt.Println("world")
10 fmt.Println("hello")
11 }
1 package main
3 import "fmt"
5 func main() {
6 for i := 0; i < 5; i++ {
7 defer fmt.Println(i)
8 }
9 }
2
1
0
1 package main
3 import (
4 "fmt"
5 "time"
6 )
8 func main() {
9 defer func(t time.Time) {
10 fmt.Println(t, time.Now())
11 }(time.Now())
12 }
When you run the above program, you can see a small difference in
time. The defer can also be used to recover from panic, which will
be discussed in the next section.
Here is an example:
Listing 3.24 : deferred panic recover
1 package main
3 import "fmt"
5 func main() {
6 defer func() {
7 if r := recover(); r != nil {
8 fmt.Println("Recoverd", r)
9 }
10 }()
11 panic("panic")
12 }
3.6 Goto
The goto statement can be used to jump control to another
statement. The location to where the control should be passed
is specified using label statements. The goto statement and the
corresponding label should be within the same function. The goto
cannot jump to a label inside another code block.
Listing 3.25 : Goto example program (goto.go)
1 package main
3 import "fmt"
5 func main() {
6 num := 10
7 goto Marker
8 num = 20
9 Marker:
10 fmt.Println("Value of num:", num)
11 }
You can save the above program in a file named goto1.go and run
it like this:
Listing 3.26 : Goto example program output
1 $ go run goto1.go
2 Value of num: 10
46 3.7. Exercises
3.7 Exercises
In the above program, the 3 and 4 are the command line arguments.
The command line arguments can be accessed from Go using the
slice available under os package. The arguments will be available
with exported name as Args and individual items can be accessed
using the index. The 0th index contains the program itself, so it can
be ignored. To access the 1st command argument use os.Args[1].
The values will be of type string which can be converted later.
package main
import (
"fmt"
"os"
"strconv"
)
func main() {
i := os.Args[1]
n, err := strconv.Atoi(i)
if err != nil {
fmt.Println("Not a number:", i)
Chapter 3. Control Structures 47
os.Exit(1)
}
if n%2 == 0 {
fmt.Printf("%d is even\n", n)
} else {
fmt.Printf("%d is odd\n", n)
}
}
import "fmt"
func main() {
for i := 1; i < 20; i++ {
if i%3 == 0 || i%5 == 0 {
fmt.Println(i)
}
}
}
Additional Exercises
Summary
Data Structures
In the last last few chapters we have seen some of the primitive
data data types. We also introduced few other advanced data types
without going to the details. In this chapter, we are going to look
into more data structures.
Zero Value
In the Quick Start chapter, you have learned various ways to declare
a variable. When you declare a variable using the var statement
without assigning a value, a default Zero value will be assigned for
certain types. The Zero value is 0 for integers and floats, empty
string for strings, and false for boolean. To demonstrate this, here
is an example:
1 package main
49
50 4.1. Primitive Data Types
3 import "fmt"
5 func main() {
6 var name string
7 var age int
8 var tall bool
9 var weight float64
10 fmt.Printf("%#v, %#v, %#v, %#v\n", name, age, tall, weight)
11 }
Variable
In the quick start chapter, we have discussed about variables and its
usage. The variable declared outside the function (package level)
can access anywhere within the same package.
Here is an example:
1 package main
3 import (
4 "fmt"
5 )
10 func main() {
11 name = "Jack"
12 fmt.Println("Name:", name)
13 fmt.Println("Country:", country)
14 }
In the above example, the name and country are two package level
variables. As we have seen above the name gets zero value, where
as value for country variable is explicitly initialized.
If the variable has been defined using the := syntax, and the user
wants to change the value of that variable, they need to use =
instead of := syntax.
If you run the below program, it’s going to throw an error:
Chapter 4. Data Structures 51
1 package main
3 import (
4 "fmt"
5 )
7 func main() {
8 age := 25
9 age := 35
10 fmt.Println(age)
11 }
$ go run update.go
# command-line-arguments
./update.go:9:6: no new variables on left side of :=
1 package main
3 import (
4 "fmt"
5 )
7 func main() {
8 age := 25
9 age = 35
10 fmt.Println(age)
11 }
Using the reflect package, you can identify the type of a variable:
1 package main
3 import (
4 "fmt"
5 "reflect"
6 )
8 func main() {
52 4.1. Primitive Data Types
9 var pi = 3.41
10 fmt.Println("type:", reflect.TypeOf(pi))
11 }
Boolean Type
• || – Logical OR
• ! – Logical NOT
Here is an example:
1 package main
3 import "fmt"
5 func main() {
6 yes := true
7 no := false
Chapter 4. Data Structures 53
Numeric Types
String Type
4.2 Constants
import (
"fmt"
)
func main() {
fmt.Println(Pi, Freezing, Name)
}
import (
"fmt"
)
const (
Freezing = true
Pi = 3.14
Name = "Tom"
)
func main() {
fmt.Println(Pi, Freezing, Name)
}
const (
Freezing = true
Pi = 3.14
Name = "Tom"
)
import (
"fmt"
)
func main() {
const Pi = 3.14
Pi = 6.86
fmt.Println(Pi)
}
iota
const (
// Illegal represents an illegal/invalid character
Illegal Token = iota
In the above example, the Token is custom type defined using the
primitive int type. The constants are defined using the factored
syntax (many constants within parenthesis). There are comments
for each constant values. Each constant value is be incremented
starting from 0. In the above example, Illegal is 0, Whitespace is
1, EOF is 2 and so on.
The iota can be used with expressions. The expression will be
repeated. Here is a good example taken from Effective Go (https:
//go.dev/doc/effective_go#constants):
type ByteSize float64
const (
// ignore first value (0) by assigning to blank identifier
_ = iota
KB ByteSize = 1 << (10 * iota)
MB
GB
TB
Chapter 4. Data Structures 57
PB
EB
ZB
YB
)
Blank Identifier
_ "github.com/lib/pq"
)
In the above example, the pq package has some code which need
to be invoked to initialize the database driver provided by that
package. And the exported functions within the above package is
supposed to be not used.
We have already seen another example where blank identifier if
used with iota to ignore certain constants.
58 4.3. Arrays
4.3 Arrays
In the above example, the length of first array is 3 and the array
values are string data. The second array contains int values. An
array’s length is part of its type, so arrays cannot be re-sized.
So, if the length is different for two arrays, those are distinct
incompatible types. The built-in len function gives the length of
array.
Array values can be accessed using the index syntax, so the
expression s[n] accesses the nth element, starting from zero.
An array values can be read like this:
colors := [3]string{"Red", "Green", "Blue"}
i := colors[1]
fmt.Println(i)
1 package main
3 import "fmt"
5 func main() {
6 colors := [3]string{"Red", "Green", "Blue"}
7 fmt.Println("Length:", len(colors))
8 for i, v := range colors {
9 fmt.Println(i, v)
10 }
11 }
If you save the above program in a file named colors.go and run
it, you will get output like this:
$ go run colors.go
Length: 3
0 Red
1 Green
2 Blue
4.4 Slices
Slice is one of most important data structure in Go. Slice is more
flexible than an array. It is possible to add and remove values from
a slice. There will be a length for slice at any time. Though the
length vary dynamically as the content value increase or decrease.
The number of values in the slice is called the length of that slice.
The slice type []T is a slice of type T. Here are two example slices:
colors := []string{"Red", "Green", "Blue"}
heights := []int{153, 146, 167, 170}
The first one is a slice of strings and the second slice is a slice of
integers. The syntax is similar to array except the length of slice is
60 4.4. Slices
not explicitly specified. You can use built-in len function to see the
length of slice.
Slice values can be accessed using the index syntax, so the
expression s[n] accesses the nth element, starting from zero.
A slice values can be read like this:
colors := []string{"Red", "Green", "Blue"}
i := colors[1]
fmt.Println(i)
1 package main
3 import "fmt"
5 func main() {
6 var v []string
7 fmt.Printf("%#v, %#v\n", v, v == nil)
8 // Output: []string(nil), true
9 }
In the above example, the value of slice v is nil. Since the slice
is nil, values cannot be accessed or set using the index. These
operations are going to raise runtime error (index out of range).
Sometimes it may not be possible to initialize a slice with some
value using the literal slice syntax given above. Go provides a
built-in function named make to initialize a slice with a given length
and zero values for all items. For example, if you want a slice with
3 items, the syntax is like this:
colors := make([]string, 3)
colors[0] = "Red"
colors[1] = "Green"
colors[2] = "Blue"
i := colors[1]
fmt.Println(i)
If you try to set value at 3rd index (colors[3]), it’s going to raise
runtime error with a message like this: ”index out of range”. Go
has a built-in function named append to add additional values. The
append function will increase the length of the slice.
Consider this example:
Listing 4.9 : Append to slice
1 package main
3 import (
4 "fmt"
5 )
7 func main() {
8 v := make([]string, 3)
9 fmt.Printf("%v\n", len(v))
10 v = append(v, "Yellow")
11 fmt.Printf("%v\n", len(v))
12 }
1 package main
3 import (
4 "fmt"
5 )
7 func main() {
8 v := make([]string, 3)
9 fmt.Printf("%v\n", len(v))
10 v = append(v, "Yellow", "Black")
11 fmt.Printf("%v\n", len(v))
12 }
The above example append two values. Though you can provide
any number of values to append.
You can use the ”...” operator to expand a slice. This can be used
to append one slice to another slice. See this example:
62 4.4. Slices
1 package main
3 import (
4 "fmt"
5 )
7 func main() {
8 v := make([]string, 3)
9 fmt.Printf("%v\n", len(v))
10 a := []string{"Yellow", "Black"}
11 v = append(v, a...)
12 fmt.Printf("%v\n", len(v))
13 }
If you append too many values to a slice using a for loop, there is
one optimization related that you need to be aware.
Consider this example:
1 package main
3 import (
4 "fmt"
5 )
7 func main() {
8 v := make([]string, 0)
9 for i := 0; i < 9000000; i++ {
10 v = append(v, "Yellow")
11 }
12 fmt.Printf("%v\n", len(v))
13 }
If you run the above program, it’s going to take few seconds to
execute. To explain this, some understanding of internal structure
of slice is required. Slice is implemented as a struct and an array
within. The elements in the slice will be stored in the underlying
array. As you know, the length of array is part of the array type. So,
when appending an item to a slice the a new array will be created.
Chapter 4. Data Structures 63
If you make this change and run the program again, you can see that
it run much faster than the earlier code. The reason for faster code
is that the slice capacity had already set with maximum required
length.
4.5 Maps
Map is another important data structure in Go. We have briefly
discussed about maps in the Quick Start chapter. As you know,
map is an implementation of hash table. The hash table is available
in many very high level languages. The data in map is organized
like key value pairs.
A variable of map can be declared like this:
var fruits map[string]int
After initializing, you can add new key value pairs like this:
fruits["Grape"] = 15
If you try to add values to maps without initializing, you will get an
error like this:
panic: assignment to entry in nil map
func main() {
var m map[string]int
m["k"] = 7
}
Here is an example:
package main
import "fmt"
func main() {
var fruits = map[string]int{
"Apple": 45,
"Mango": 24,
"Orange": 34,
}
fmt.Println(fruits["Apple"])
}
If the key doesn’t exist, a zero value will be returned. For example,
in the below example, value of pineappleCount is going be 0.
package main
import "fmt"
func main() {
var fruits = map[string]int{
"Apple": 45,
"Mango": 24,
"Orange": 34,
}
pineappleCount := fruits["Pineapple"]
fmt.Println(pineappleCount)
}
Chapter 4. Data Structures 65
If you need to check if the key exist, the above syntax can be
modified to return two values. The first one would be the actual
value or zero value and the second one would be a boolean
indicating if the key exists.
package main
import "fmt"
func main() {
var fruits = map[string]int{
"Apple": 45,
"Mango": 24,
"Orange": 34,
}
_, ok := fruits["Pineapple"]
fmt.Println(ok)
}
import "fmt"
func main() {
var fruits = map[string]int{
"Apple": 45,
"Mango": 24,
"Orange": 34,
}
if _, ok := fruits["Pineapple"]; ok {
fmt.Println("Key exists.")
} else {
fmt.Println("Key doesn't exist.")
}
}
To see the number of key/value pairs, you can use the built-in len
function.
package main
import "fmt"
func main() {
var fruits = map[string]int{
"Apple": 45,
66 4.6. Custom Data Types
"Mango": 24,
"Orange": 34,
}
fmt.Println(len(fruits))
}
import "fmt"
func main() {
var fruits = map[string]int{
"Apple": 45,
"Mango": 24,
"Orange": 34,
}
delete(fruits, "Orange")
fmt.Println(len(fruits))
}
Apart from the built-in data types, you can create your own custom
data types. The type keyword can be used to create custom types.
Here is an example.
package main
import "fmt"
func main() {
a := age(2)
fmt.Println(a)
fmt.Printf("Type: %T\n", a)
}
If you run the above program, the output will be like this:
Chapter 4. Data Structures 67
$ go run age.go
2
Type: age
Structs
As you can see above, the Person struct is defined using type and
struct keywords. Within the curly brace, attributes with other
types are defined. If you avoid attributes, it will become an empty
struct.
Here is an example empty struct:
type Empty struct {
}
You can also use named attributes. In the case of named attributes,
if you miss any values, the default zero value will be initialized.
p3 := Person{Name: "Huck"}
p4 := Person{Age: 10}
In the next, we are going to learn about funtions and methods. That
chapter expands the discussion about custom types bevior changes
through funtions associated with custom types called strcuts.
It is possible to embed structs inside other structs. Here is an
example:
68 4.7. Pointers
4.7 Pointers
When you are passing a variable as an argument to a function, Go
creates a copy of the value and send it. In some situations, creating
a copy will be expensive when the size of object is large. Another
scenario where pass by value is not feasible is when you need to
modify the original object inside the function. In the case of pass
by value, you can modify it as you are getting a new object every
time. Go supports another way to pass a reference to the original
value using the memory location or address of the object.
To get address of a variable, you can use & as a prefix for the
variable. Here in an example usage:
a := 7
fmt.Printf("%v\n", &a)
To get the value back from the address, you can use * as a prefix
for the variable. Here in an example usage:
a := 7
b := &a
fmt.Printf("%v\n", *b)
1 package main
3 import (
4 "fmt"
5 )
12 fmt.Printf("%v\n", a)
13 }
15 func main() {
16 a := 4
17 fmt.Printf("%v\n", &a)
18 value(a)
19 pointer(&a)
20 }
As you can see above, the second output is different from the first
and third. This is because a value is passed instead of a pointer.
And so when we are printing the address, it’s printing the address
of the new variable.
In the functions chapter, the section about methods (section 5.6)
explains the pointer receiver.
new
In this one string pointer variable is declared, but it’s not allocated
with zeror value. It will have nil value and so it cannot be
dereferenced. If you try to reference, without allocating using the
new function, you will get an error like this:
panic: runtime error: invalid memory address or nil pointer
dereference
4.8 Exercises
Exercise 1: Create a custom type for circle using float64 and define
Area and Perimeter.
Solution:
package main
import "fmt"
func main() {
c := Circle(4.0)
fmt.Println(c.Area())
fmt.Println(c.Perimeter())
}
The custom Circle type is created using the built-in float64 type.
It would be better if the circle is defined using a struct. Using struct
helps to change the structure later with additional attributes. The
struct will look like this:
type Circle struct {
Radius float64
}
import "fmt"
Chapter 4. Data Structures 71
func main() {
persons := []Person{
Person{Name: "Huck", Age: 11},
Person{Name: "Tom", Age: 10},
Person{Name: "Polly", Age: 52},
}
fmt.Println(persons)
}
Additional Exercises
Summary
Functions
2
f(r) = 3.14r
Figure 5.1: Mathematical function for area of a circle
This function square the input value and multiply with 3.14.
Depending on the input value the output varies.
As you can see in the diagram, r is the input and y is the output.
A function in Go can take input arguments and perform actions
and return values. A minimal implementation of this function in
Go looks like this.
func Area(r float64) float64 {
return 3.14 * r * r
}
73
74
f(r)
r 2 y
3.14r
1 package main
3 import "fmt"
9 func main() {
10 area := Area(5.0)
11 fmt.Println(area)
12 }
5.1 Parameters
1 package main
3 import (
4 "fmt"
5 "time"
6 )
15 func main() {
16 now := TimeNow()
17 fmt.Println(now)
18 }
More parameters
1 package main
3 import "fmt"
6 return a + b
7 }
9 func main() {
10 s := sum(5, 2)
11 fmt.Println(s)
12 }
The above sum function accepts two integer parameters. Since both
parameters are integers, the type can be specified once.
func sum(a, b int) int {
return a + b
}
1 package main
3 import "fmt"
9 func main() {
10 v, r := div(5, 2)
11 fmt.Println(v, r)
12 }
In the above example, the div function return two values. So two
variables are used to assign the values. If you use one variable
it will produce compile time error. The compile time error will
be produced, if more than two variables are used to assigned.
However, it is possible to call the function without assigning to any
variables.
v, _ := div(5, 2)
div(5, 2)
Chapter 5. Functions 77
1 package main
3 import "fmt"
11 func main() {
12 v, r := div(5, 2)
13 fmt.Println(v, r)
14 }
1 package main
3 import "fmt"
14 func main() {
15 sum(1, 2)
16 sum(1, 2, 3)
17 nums := []int{1, 2, 3, 4}
18 sum(nums...)
19 }
As you can see the arguments are captured into a slice. You can
send values in a slice to a variadic function using the ellipsis syntax
as a suffix.
1 package main
3 import "fmt"
5 func main() {
6 name := "Tom"
7 func() {
Chapter 5. Functions 79
8 fmt.Println("Hello", name)
9 }()
10 }
1 package main
3 import "fmt"
8 func main() {
9 name := "Tom"
10 func() {
11 fmt.Println("Hello", name)
12 }()
13 }
5.6 Methods
import (
"fmt"
"os"
"strconv"
)
return true
} else {
return false
}
func main() {
i := os.Args[1]
n, err := strconv.Atoi(i)
if err != nil {
fmt.Println("Not a number:", i)
os.Exit(1)
}
num := Number(n)
fmt.Println(num.Even())
}
You can call these methods from the struct initialized using the
Rectangle struct. Here is an example:
r := Rectangle{3.0, 5.0}
area := r.Area()
perimeter := r.Perimeter()
Chapter 5. Functions 81
1 package main
3 import "fmt"
13 func main() {
14 c := Circle{3.4}
15 a := c.Area()
16 fmt.Println(a)
17 }
In the above example, the method Area calculate area for a circle.
The receiver could be pointer also. Here is a modified example with
pointer receiver:
1 package main
3 import "fmt"
13 func main() {
14 c1 := Circle{3.4}
15 a1 := c1.Area()
16 fmt.Println(a1)
18 c2 := &Circle{3.4}
19 a2 := c2.Area()
20 fmt.Println(a2)
22 }
name := new(Temperature)
As you can see, the temperature value is set to -7.6 and assigned
to the variable.
Chapter 5. Functions 83
5.7 Exercises
Exercise 1: Write a method to calculate the area of a rectangle for
a given struct with width and height.
Solution:
type Rectangle struct {
Width float64
Height float64
}
Additional Exercises
Answers to these additional exercises are given in the Appendix A.
Problem 1: Write a program with a function to calculate the
perimeter of a circle.
Summary
This chapter explained the main features of functions in Go. It
covered how to send input parameters and receive return values. It
also explained about variadic functions and anonymous functions.
This chapter briefly covered methods. The next chapter will cover
interfaces. Along with that, we will learn more about methods.
Brief summary of key concepts introduced in this chapter:
Objects
If any type satisfy this interface - that is define these two methods
which returns float64 - then, we can say that type is implementing
this interface. One difference with many other languages with
interface support and Go is that, in Go implementing an interface
happens implicitly. So, no need to explicitly declare a type is
implementing a particular interface.
To understand this idea, consider this Rectangle struct type:
type Rectangle struct {
Width float64
85
86
Height float64
}
As you can see above, the above Rectangle type has two methods
named Area and Perimeter which returns float64. So, we can say
Rectangle is implementing the Geometry interface. To elaborate
the example further, we will create one more implementation:
type Circle struct {
Radius float64
}
When you call the above function, you can pass the argument as
an object of Geometry interface type. Since both Rectangle and
Circle satisfy that interface, you can use either one of them.
Here is a code which call Measure function with Rectangle and
Circle objects:
r := Rectangle{Width: 2.5, Height: 4.0}
c := Circle{Radius: 6.5}
Measure(r)
Measure(c)
Chapter 6. Objects 87
In Go, a type can implement more than one interface. If a type has
methods that satisfy different interfaces, we can say that that type
is implementing those interfaces.
Consider this interface:
type Stringer interface {
String() string
}
blackHole(1)
blackHole("Hello")
blackHole(struct{})
Since the Println accepts empty interfaces, you could pass any
type arguments like this:
fmt.Println(1, "Hello", struct{})
func main() {
v := Temperature{35.6, "Bangalore"}
cityTemperature(&v)
}
In the above example, if you want to print the width and and height
from the Measure function, you can use type assertions.
Type assertion gives the underlying concrete value of an interface
type. In the above example, you can access the rectangle object
like this:
r := g.(Rectangle)
fmt.Println("Width:", r.Width)
fmt.Println("Height:", r.Height)
6.6 Exercises
Solution:
package main
import (
"encoding/json"
"fmt"
"strings"
)
func main() {
p := Person{Name: "Baiju", Place: "Bangalore"}
o, err := json.Marshal(p)
if err != nil {
panic(err)
}
fmt.Println(string(o))
}
Additional Exercises
Summary
Concurrency
If you observe, you could see many things happening around you at
any given time. This is how the world function - the train is gently
moving, passengers talking each other, farmers working in the field
and many other things are happening simultaneously. We can say,
the world we live in function concurrently.
Go has built-in concurrency features with syntax support. The
Go concurrency is inspired by a paper published in 1978 by Tony
Hoare. The paper title is Communicating sequential processes 1 .
Go has some new terminologies and keywords related to concurrent
programming. The two important words are goroutine and channel.
This chapter will go through these concepts and walk through some
examples to further explain concurrency in Go.
The Go runtime is part of the executable binary created when
compiling any Go code. The Go runtime contains a garbage
collector and a scheduler to manage lightweight threads called
Goroutines. Goroutine is a fundamental abstraction to support
concurrency. Goroutine is an independently executing part of the
program. You can invoke any number of goroutines and all of them
could run concurrently.
Goroutines can communicate to each other via typed conduits
called channels. Channels can be used to send and receive data.
1 http://usingcsp.com
93
94 7.1. Goroutine
7.1 Goroutine
Goroutine is like a process running in the background. A function
with go keyword as prefix starts the goroutine. Any function
including anonymous function can be invoked with go keyword. In
fact, the main function is a special goroutine invoked during the
starup of any program by the Go runtime.
To understand the Goroutine better let’s look at a simple program:
Listing 7.1 : Goroutine with explicit sleep
1 package main
3 import (
4 "fmt"
5 "time"
6 )
10 func setMessage() {
11 msg = "Hello, World!"
12 }
14 func main() {
15 go setMessage()
16 time.Sleep(1 * time.Millisecond)
17 fmt.Println(msg)
18 }
7.2 Channels
Multiple goroutines can communicate using channels. Channels
can be used to send and receive any type of values. You can send
and receive values with this channel operator: <-
Chapter 7. Concurrency 95
1 package main
3 import (
4 "fmt"
5 )
10 func setMessage() {
11 msg = "Hello, World!"
12 c <- 0
13 }
15 func main() {
16 go setMessage()
17 <-c
18 fmt.Println(msg)
19 }
7.3 Waitgroups
Go standard library has a sync package which provides few
synchronization primitives. One of the mechanism is Waitgroups
which can be used to wait for multiple goroutines to complete.
The Add function add the number of goroutines to wait for. At the
end of these goroutines call Done function to indicate the task has
completed. The Wait function call, block further operations until
all goroutines are completed.
Here is a modified version of the previous example using
Waitgroups.
1 package main
3 import (
4 "fmt"
5 "sync"
6 )
11 func setMessage() {
12 msg = "Hello, World!"
13 wg.Done()
14 }
16 func main() {
17 wg.Add(1)
18 go setMessage()
19 wg.Wait()
20 fmt.Println(msg)
21 }
1 package main
Chapter 7. Concurrency 97
3 import (
4 "fmt"
5 "sync"
6 "time"
7 )
14 func main() {
15 var wg sync.WaitGroup
16 for i := 0; i < 5; i++ {
17 wg.Add(1)
18 go func(j int) {
19 defer wg.Done()
20 someWork(j)
21 }(i)
22 }
23 wg.Wait()
24 }
7.4 Select
The select is a statement with some similarity to switch, but used
with channels and goroutines. The select statement lets a goroutine
wait on multiple communication operations through channels.
Under a select statement, you can add multiple cases. A select
statement blocks until one of its case is available for run – that is
the channel has some value. If multiple channels used in cases has
value readily avaibale, select chooses one at random.
Here is an example:
package main
import "time"
import "fmt"
func main() {
c1 := make(chan string)
c2 := make(chan string)
go func() {
time.Sleep(time.Second * 1)
98 7.5. Buffered Channels
c1 <- "one"
}()
go func() {
time.Sleep(time.Second * 2)
c2 <- "two"
}()
1 package main
3 import "fmt"
5 func main() {
6 ch := make(chan string, 2)
7 ch <- "Hello"
8 ch <- "World"
9 fmt.Println(<-ch)
10 fmt.Println(<-ch)
11 }
Chapter 7. Concurrency 99
1 package main
3 import "fmt"
13 func main() {
14 n := make(chan string)
16 go func() {
17 fmt.Println(<-n)
18 }()
20 sendOnly(n)
22 go func() {
23 n <- "Hello"
24 }()
26 receiveOnly(n)
27 }
variable which can be only user for receive data. You cannot send
any value to that channel from that function.
type DB struct{}
var db *DB
var once sync.Once
If the above GetDB function is called multiple times, only once the
DB object will get constructed.
7.8 Exercises
Exercise 1: Write a program to download a list of web pages
concurrently using Goroutines.
Hint: Use this tool for serving junk content for testing: https://
github.com/baijum/lipsum
Solution:
package main
import (
"io/ioutil"
"log"
"net/http"
"net/url"
"sync"
Chapter 7. Concurrency 101
func main() {
urls := []string{
"http://localhost:9999/1.txt",
"http://localhost:9999/2.txt",
"http://localhost:9999/3.txt",
"http://localhost:9999/4.txt",
}
var wg sync.WaitGroup
for _, u := range urls {
wg.Add(1)
go func(u string) {
defer wg.Done()
ul, err := url.Parse(u)
fn := ul.Path[1:len(ul.Path)]
res, err := http.Get(u)
if err != nil {
log.Println(err, u)
}
content, _ := ioutil.ReadAll(res.Body)
ioutil.WriteFile(fn, content, 0644)
res.Body.Close()
}(u)
}
wg.Wait()
}
Additional Exercises
Summary
Packages
That clause was declaring the name of the package as main. The
package name was main because the program was executable. For
non-executable programs, you can give a different meaningful short
name as the package name.
Most of the previous programs imported other packages like this:
import "fmt"
103
104 8.1. Creating a Package
1 package main
1 package main
1 package main
3 import "fmt"
Chapter 8. Packages 105
5 var c Circle
7 func main() {
8 c = Circle{3.5}
9 fmt.Println(c.Area())
10 }
As you can see above all the above source files belongs to main
package.
You can build and run it like this:
$ go build -o circle
$ ./circle
38.465
var a = b + 2
var b = 1
1 package config
3 import (
4 "log"
6 "github.com/kelseyhightower/envconfig"
7 )
16 func init() {
17 err := envconfig.Process("app", &Config)
18 if err != nil {
19 log.Fatal(err.Error())
20 }
21 }
/*
Package http provides HTTP client and server implementations.
Get, Head, Post, and PostForm make HTTP (or HTTPS) requests:
<...strip lines...>
*/
package http
Many lines are stripped from the above example where its marked.
As you can see the above documentation file starts with copyright
notice. But the copyright notice is using single line comment
multiple times. This notice will be ignored when generating
documentation. And the documentation is written within multi-line
comments. At the end of file, the package name is also declared.
• https://github.com/auth0/go-jwt-middleware
• https://github.com/blevesearch/bleve
• https://github.com/dgrijalva/jwt-go
• https://github.com/elazarl/go-bindata-assetfs
• https://github.com/google/jsonapi
• https://github.com/gorilla/mux
• https://github.com/jpillora/backoff
• https://github.com/kelseyhightower/envconfig
• https://github.com/lib/pq
108 8.5. Module
• https://github.com/pkg/errors
• https://github.com/thoas/stats
• https://github.com/urfave/negroni
You can use go get command to get these packages locally. Go 1.11
release introduced the module support. The modules can be used
to manage external dependant packages.
8.5 Module
Creating a module
The Go tool has support for creating modules. You can use the mod
command to manage module.
To initialize a new module, use mod init command with name of
module as argument. Normally the name will be same as the
publishing location. Here is an example:
mkdir hello
cd hello
go mod init github.com/baijum/hello
Chapter 8. Packages 109
go 1.20
import (
"database/sql"
_ "github.com/lib/pq"
)
func main() {
sql.Open("postgres",
"host=lh port=5432 user=gt dbname=db password=secret")
}
Note: The imported package is the driver used by the sql package.
You will see the mod.mod updated with dependency.
module github.com/github.com/hello
go 1.20
Type alias can be removed once all the dependant packages migrate
to use import path.
8.7 Exercises
As you can see above, the source file starts with package
documentation. Also you can see the documentation for all
exported functions.
Additional Exercises
Summary
form a single unit. They are one of the building blocks of a reusable
Go program. This chapter explained how to create packages,
document packages, and publish packages. It also covered modules
and their usage. Finally, it explained how to move types across
packages during refactoring. By understanding packages, you can
write more modular and reusable Go programs.
112
Chapter 9
Input/Output
113
114 9.2. Files
will be other files and devices. You can access all the command
line arguments using the Args array/slice attribute available in os
package. Here is an example:
package main
import (
"fmt"
"os"
)
func main() {
fmt.Println(os.Args)
fmt.Println(os.Args[0])
fmt.Println(os.Args[1])
fmt.Println(os.Args[2])
}
You can run this program with minimum two additional arguments:
$ go build cmdline.go
$ ./cmdline one -two
[cmdline one -two]
./cmdline
one
-two
As you can see, it’s difficult to parse command line arguments like
this. Go standard library has a package named flag which helps to
easily parse command line arguments. This chapter has a section
to explain the flag package.
9.2 Files
1 package main
3 import (
4 "fmt"
5 "io"
6 "os"
7 )
9 func main() {
10 fd, err := os.Open("poem.txt")
11 if err != nil {
12 fmt.Println("Error reading file:", err)
13 }
14 for {
15 chars := make([]byte, 50)
16 n, err := fd.Read(chars)
17 if n == 0 && err == io.EOF {
18 break
19 }
20 fmt.Print(string(chars))
21 }
22 fd.Close()
23 }
When you run this program, you will get the whole text as output. In
the line number 10, the Open function from the os package is called
to open the file for reading. It returns a file descriptor1 and error.
In the line number 14, an infinite loop is stared to read the content.
Line 15 initialize a slice of bytes of length 50. The fd.Read method
reads the given length of characters and writes to the given slice. It
returns the number of characters read and error. The io.EOF error
is returned when end of file is reached. This is used as the condition
to break the loop.
Here is a program to write some text to a file:
Listing 9.2 : Write to file
1 package main
3 import (
4 "fmt"
5 "os"
6 )
1 https://en.wikipedia.org/wiki/File_descriptor
116 9.3. Standard Streams
8 func main() {
9 fd, err := os.Create("hello.txt")
10 if err != nil {
11 fmt.Println("Cannot write file:", err)
12 os.Exit(1)
13 }
14 fd.Write([]byte("Hello, World!\n"))
15 fd.Close()
16 }
1 package main
3 import "fmt"
5 func main() {
6 var name string
7 fmt.Printf("Enter your name: ")
8 fmt.Scanf("%s", &name)
9 fmt.Println("Your name:", name)
10 }
2 https://en.wikipedia.org/wiki/Standard_streams
Chapter 9. Input/Output 117
The Scanf function reads the standard input. The first argument is
the format and the second one is the pointer variable. The value
read from standard input cab be accessed using the given variable.
You can run the above program in different ways:
$ go run code/io/readprint.go
Enter your name: Baiju
Your name: Baiju
$ echo "Baiju" |go run code/io/readprint.go
Enter your name: Your name: Baiju
$ go run code/io/readprint.go << EOF
> Baiju
> EOF
Enter your name: Your name: Baiju
$ echo "Baiju" > /tmp/baiju.txt
$ go run code/io/readprint.go < /tmp/baiju.txt
Enter your name: Your name: Baiju
As you can see from this program, the Printf function writes to
standard output and the Scanf reads the standard input. Go can
also writes to standard error output stream.
The io package provides a set of interfaces and functions that allow
developers to work with different types of input and output streams.
Consider a use case to convert everything that comes to standard
input to convert to upper case. This can be achieved by reading
all standard input using io.ReadAll and converting to upper case.
Here is code:
Listing 9.4 : Convert standard input to upper case
1 package main
3 import (
4 "fmt"
5 "io"
6 "os"
7 "strings"
8 )
10 func main() {
11 stdin, err := io.ReadAll(os.Stdin)
12 if err != nil {
13 panic(err)
14 }
15 str := string(stdin)
16 newStr := strings.TrimSuffix(str, "\n")
17 upper := strings.ToUpper(newStr)
18 fmt.Println(upper)
19 }
118 9.4. Using flag Package
You can run this program similar to how you did with the previous
program.
You can use fmt.Fprintf with os.Stderr as the first argument to write
to standard error.
fmt.Fprintf(os.Stderr, "This goes to standard error: %s", "OK")
The above code snippet defines an integer flag with name as count
and it is stored in a variable with the name as pageCount. The type
of the variable is *int. Similar to this integer flag, you can defines
flags of other types.
Once all the flags are defined, you can parse it like this:
flag.Parse()
The above Parse function call parse the command line arguments
and store the values in the given variables.
Once the flags are parsed, you can dereference it like this:
fmt.Println("pageCount: ", *pageCount)
1 package main
3 import (
4 "fmt"
5 )
7 func main() {
8 fmt.Printf("Value: %v, Type: %T\n", "Baiju", "Baiju")
9 fmt.Printf("Value: %v, Type: %T\n", 7, 7)
10 fmt.Printf("Value: %v, Type: %T\n", uint(7), uint(7))
11 fmt.Printf("Value: %v, Type: %T\n", int8(7), int8(7))
12 fmt.Printf("Value: %v, Type: %T\n", true, true)
13 fmt.Printf("Value: %v, Type: %T\n", 7.0, 7.0)
14 fmt.Printf("Value: %v, Type: %T\n", (1 + 6i), (1 + 6i))
15 }
The %T shows the type of the value. The output of the above
program will be like this.
Value: Baiju, Type: string
Value: 7, Type: int
Value: 7, Type: uint
Value: 7, Type: int8
Value: true, Type: bool
Value: 7, Type: float64
Value: (1+6i), Type: complex128
If you use a %+v as the format string for struct it shows the field
names. See this example:
1 package main
3 import (
4 "fmt"
5 )
11 }
13 func main() {
14 c := Circle{radius: 76.45, color: "blue"}
15 fmt.Printf("Value: %#v\n", c)
16 fmt.Printf("Value with fields: %+v\n", c)
17 fmt.Printf("Type: %T\n", c)
18 }
If you run the above program, the output is going to be like this:
Value: {76.45 blue}
Value with fields: {radius:76.45 color:blue}
Type: main.Circle
As you can see from the output, in the first line %v doesn’t show the
fields. But in the second line, %+v shows the struct fields.
The %#v shows the representation of the value. Here is a modified
version of above program to print few values of primitive type.
1 package main
3 import (
4 "fmt"
5 )
7 func main() {
8 fmt.Printf("Value: %#v, Type: %T\n", "Baiju", "Baiju")
9 fmt.Printf("Value: %#v, Type: %T\n", 7, 7)
10 fmt.Printf("Value: %#v, Type: %T\n", uint(7), uint(7))
11 fmt.Printf("Value: %#v, Type: %T\n", int8(7), int8(7))
12 fmt.Printf("Value: %#v, Type: %T\n", true, true)
13 fmt.Printf("Value: %#v, Type: %T\n", 7.0, 7.0)
14 fmt.Printf("Value: %#v, Type: %T\n", (1 + 6i), (1 + 6i))
15 }
If you want a literal % sign, use two % signs next to each other. Here
is a code snippet:
fmt.Println("Tom scored 92%% marks")
1 package main
3 import (
4 "fmt"
5 "strconv"
6 )
19 func main() {
20 temp := Temperature{30.456, "Celsius"}
21 fmt.Println(temp)
22 fmt.Printf("%v\n", temp)
23 fmt.Printf("%+v\n", temp)
24 fmt.Printf("%#v\n", temp)
25 }
9.6 Exercises
import (
"flag"
"fmt"
"log"
"os"
)
func main() {
flag.Parse()
if *length <= 0 {
log.Println("Invalid length")
os.Exit(1)
}
if *width <= 0 {
log.Println("Invalid width")
os.Exit(1)
}
r := Rectangle{Length: *length, Width: *width}
a := r.Area()
fmt.Println("Area: ", a)
}
Area: 8.5
Additional Exercises
Summary
Testing
1 package hello
3 import "testing"
In the first line, the package name is hello which is the same as the
package where the function is going to be defined. Since both test
and actual code is in the same package, the test can access any
name within that package irrespective whether it is an exported
name or not. At the same, when the actual problem is compiled
125
126
using the go build command, the test files are ignored. The build
ignores all files with the name ending _test.go. Sometimes these
kinds of tests are called white-box test as it is accessing both
exported and unexported names within the package. If you only
want to access exported names within the test, you can declare
the name of the test package with _test suffix. In this case, that
would be hello_test, which should work in this case as we are
only testing the exported function directly. However, to access
those exported names – in this case, a function – the package should
import explicitly.
The line no. 5 starts the test function declaration. As per the test
framework, the function name should start with Test prefix. The
prefix is what helps the test runner to identify the tests that should
be running.
The input parameter t is a pointer of type testing.T. The
testing.T type provides functions to make the test pass or fail.
It also gives functions to log messages.
In the line no. 6 the Hello function is called with ”Tom” as the input
string. The return value is assigning to a variable named out.
In line no. 7 the actual output value is checked against the expected
output value. If the values are not matching, the Fail function is
getting called. The particular function state is going to be marked
as a failure when the Fail function is getting called.
The test is going to pass or fail based on the implementation of the
Hello function. Here is an implementation of the function to satisfy
the test:
Listing 10.2 : Hello function
1 package hello
3 import "fmt"
The testing.T has two functions for logging, one with default
formatting and the other with the user-specified format. Both
functions accept an arbitrary number of arguments.
The Log function formats its arguments using the default formats
available for any type. The behavior is similar to fmt.Println
function. So, you can change the formatted value by implementing
the fmt.Stringer interface:
type Stringer interface {
String() string
}
128 10.3. Failing with Log Message
In the above function call, there are only two arguments given, but
you can pass any number of arguments.
The log message going to print if the test is failing. The
verbose mode, the -v command-line option, also log the message
irrespective of whether a test fails or not.
The Logf function takes a string format followed by arguments
expected by the given string format. Here is an example:
t.Logf("%d no. of lines: %s", 34, "More number of lines")
The Logf formats the values based on the given format. The Logf
is similar to fmt.Printf function.
When writing tests, there are situations where particular tests need
not run. Some tests might have written for a specific environment.
The criteria for running tests could be CPU architecture, operating
system or any other parameter. The testing package has functions
to mark test for skipping.
The SkipNow function call marks the test as having been skipped.
It stops the current test execution. If the test has marked as failed
before skipping, that particular test is yet considered to have failed.
The SkipNow function doesn’t accept any argument. Here is a
simple example:
1 package hello
3 import (
4 "runtime"
5 "testing"
6 )
If you run the above code on a Linux system, you can see the test
has skipped. The output is going to be something like this:
$ go test . -v
=== RUN TestHello
--- SKIP: TestHello (0.00s)
PASS
ok _/home/baiju/skip 0.001s
As you can see from the output, the test has skipped execution.
There are two more convenient functions similar to Error and
Errorf. Those functions are Skip and Skipf. These functions help
you to log a message before skipping. The message could be the
reason for skipping the test.
Here is an example:
130 10.5. Parallel Running
You can mark a test to run in parallel. To do so, you can call the
t.Parallel function. The test is going to run in parallel to other
tests marked parallel.
Subtests are reported separately from each other. This means that
if a subtest fails, the test runner will report the failure for that
subtest only. The parent test will still be considered to have passed.
Subtests can be used to test different aspects of a function or
method. For example, you could use subtests to test different input
values, different output values, or different error conditions.
Subtests can also be used to test different implementations of
a function or method. For example, you could use subtests to
test a function that is implemented in Go and a function that is
implemented in C.
Chapter 10. Testing 131
10.7 Exercises
Exercise 1: Create a package with a function to return the sum of
two integers and write a test for the same.
Solution:
sum.go:
package sum
sum_test.go:
package sum
import "testing"
Additional Exercises
Answers to these additional exercises are given in the Appendix A.
Problem 1: Write a test case program to fail the test and not
continue with the remaining tests.
Summary
This chapter explained how to write tests using the testing package.
It covered how to mark a test as a failure, how to log messages,
how to skip tests, and how to run tests in parallel. It also briefly
mentioned sub-tests.
132
Chapter 11
Tooling
Good support for lots of useful tools is another strength of Go. Apart
from the built-in tools, there any many other community-built tools.
This chapter covers the built-in Go tools and few other external
tools.
The built-in Go tools can access through the go command. When
you install the Go compiler (gc); the go tool is available in the path.
The go tool has many commands. You can use the go tool to compile
Go programs, run test cases, and format source files among other
things.
The help command also provides help for specific topics like
”buildmode”, ”cache”, ”filetype”, and ”environment” among other
topics. To see help for a specific topic, you can run the command
like this:
go help environment
133
134 11.2. Basic Information
Version
Environment
The output should display all the environment variables used by the
Go tool when running different commands.
A typical output will look like this:
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/baiju/.cache/go-build"
GOENV="/home/baiju/.config/go/env"
GOEXE=""
GOEXPERIMENT=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOINSECURE=""
GOMODCACHE="/home/baiju/go/pkg/mod"
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/home/baiju/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/local/go"
Chapter 11. Tooling 135
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64"
GOVCS=""
GOVERSION="go1.20.4"
GCCGO="gccgo"
GOAMD64="v1"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD="/dev/null"
GOWORK=""
CGO_CFLAGS="-O2 -g"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-O2 -g"
CGO_FFLAGS="-O2 -g"
CGO_LDFLAGS="-O2 -g"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0
-fdebug-prefix-map=/tmp/go-build1378738152=/tmp/go-build
-gno-record-gcc-switches"
List
If you want to set the executable binary file name, use the -o option:
go build -o myprog
If you want to build and run at once, you can use the "run"
command:
go run program.go
You can specify more that one Go source file in the command line
arguments:
go run file1.go file2.go file3.go
Of course, when you specify more than one file names, only one
”main” function should be defined among all of the files.
Conditional Compilation
In the above example, the file will compile on 32-bit x86 Linux
system.
// +build linux darwin
The Go tool has a built-in test runner. To run tests for the current
package, run this command:
go test
.
|-- hello.go
|-- hello_test.go
|-- sub1
| |-- sub1.go
| `-- sub1_test.go
`-- sub2
|-- sub2.go
`-- sub2_test.go
If you run go test from the top-level directory, it’s going to run
tests in that directory, and not any sub directories. You can specify
directories as command line arguments to go test command to run
tests under multiple packages simultaneously. In the above listed
case, you can run all tests like this:
go test . ./sub1 ./sub2
Instead of listing each packages separates, you can use the ellipsis
syntax:
go test ./...
The above command run tests under current directory and its child
directories.
By default go test shows very few details about the tests.
$ go test ./...
ok _/home/baiju/code/mypkg 0.001s
ok _/home/baiju/code/mypkg/sub1 0.001s
--- FAIL: TestSub (0.00s)
FAIL
FAIL _/home/baiju/code/mypkg/sub2 0.003s
In the above results, it shows the name of failed test. But details
about other passing tests are not available. If you want to see
verbose results, use the -v option.
$ go test ./... -v
=== RUN TestHello
--- PASS: TestHello (0.00s)
PASS
ok _/home/baiju/code/mypkg 0.001s
=== RUN TestSub
--- PASS: TestSub (0.00s)
PASS
ok _/home/baiju/code/mypkg/sub1 0.001s
=== RUN TestSub
--- FAIL: TestSub (0.00s)
FAIL
FAIL _/home/baiju/code/mypkg/sub2 0.002s
Chapter 11. Tooling 139
If you need to filter tests based on the name, you can use the -run
option.
$ go test ./... -v -run Sub
testing: warning: no tests to run
PASS
ok _/home/baiju/code/mypkg 0.001s [no tests to run]
=== RUN TestSub
--- PASS: TestSub (0.00s)
PASS
ok _/home/baiju/code/mypkg/sub1 0.001s
=== RUN TestSub
--- FAIL: TestSub (0.00s)
FAIL
FAIL _/home/baiju/code/mypkg/sub2 0.002s
As you can see above, the TestHello test was skipped as it doesn’t
match ”Sub” pattern.
The chapter on testing has more details about writing test cases.
golangci-lint is a handy program to run various lint tools and
normalize their output. This program is useful to run through
continuous integration. You can download the program from here:
https://github.com/golangci/golangci-lint
The command will format source files and write it back to the same
file. Also it will list the files that is formatted.
140 11.6. Generating Code
When you build the program, the parser will be generated and will
be part of the code that compiles.
//go:embed database-schema.sql
var dbSchema string
//go:embed logo.jpg
var logo []byte
Chapter 11. Tooling 141
If you need an entire directory, you can use the embed.FS as the
type:
import "embed"
//go:embed static
var content embed.FS
or a type:
go doc strings.Reader
Or a method:
go doc strings.Reader.Size
1 package main
3 import (
4 "fmt"
5 )
7 func main() {
8 v := 1
9 fmt.Printf("%#v %s\n", v)
10 }
If you compile and run it. It’s going to be give some output. But if
you observe the code, there is an unnecessary %s format string.
If you run vet command, you can see the issue:
$ go vet susp.go
# command-line-arguments
./susp.go:9: Printf format %s reads arg #2,
but call has only 1 arg
Note: The vet command is automatically run along with the test
command.
11.10 Exercises
hello_test.go:
package hello
import "testing"
Additional Exercises
Summary
import "fmt"
func main() {
h := StartsCapital("Hello")
fmt.Println(h)
w := StartsCapital("world")
fmt.Println(w)
}
import "fmt"
145
146 Appendix A: Answers
func main() {
Fib(200)
}
import (
"fmt"
"time"
)
func main() {
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("Good morning!")
case t.Hour() < 17:
fmt.Println("Good afternoon.")
default:
fmt.Println("Good evening.")
}
}
import (
"fmt"
)
Appendix A: Answers 147
func main() {
n := 7
found := false
for _, j := range []int{2, 3, 5} {
if n%j == 0 {
fmt.Printf("%d is a multiple of %d\n", n, j)
found = true
}
}
if !found {
fmt.Printf("%d is not a multiple of 2, 3, or 5\n", n)
}
}
import "fmt"
func main() {
temperatures := map[string]Temperature{
"New York": 9.3,
"London": 13.5,
"New Delhi": 31.5,
"Montreal": -9.0,
}
location = "Montreal"
148 Appendix A: Answers
fmt.Println(location, temperatures[location].Freezing())
import "fmt"
func main() {
countries := map[string]Country{}
countries["India"] = Country{Capital: "New Delhi",
Currency: "Indian Rupee", Popolation: 1428600000}
fmt.Printf("%#v\n", countries)
}
Chapter 5: Functions
import "fmt"
func main() {
Appendix A: Answers 149
c := Circle{5.0}
fmt.Println(c.Area())
}
Chapter 6: Objects
Solution:
package main
import "fmt"
func main() {
err := SomeAction()
if err != nil {
fmt.Println(err)
}
}
Chapter 7: Concurrency
Problem 1: Write a program to watch log files and detect any entry
with a particular word.
Solution:
150 Appendix A: Answers
package main
import (
"bufio"
"fmt"
"os"
"os/signal"
"strings"
"time"
)
f, err := os.Open(fp)
if err != nil {
return err
}
r := bufio.NewReader(f)
defer f.Close()
for {
line, err := r.ReadBytes('\n')
if err != nil {
if err.Error() == "EOF" {
time.Sleep(2 * time.Second)
continue
}
fmt.Printf("Error: %s\n%v\n", line, err)
}
if strings.Contains(string(line), word) {
fmt.Printf("%s: Matched: %s\n", fp, line)
}
time.Sleep(2 * time.Second)
}
}
func main() {
word := os.Args[1]
files := []string{}
for _, f := range os.Args[2:len(os.Args)] {
files = append(files, f)
go watch(word, f)
}
sig := make(chan os.Signal, 1)
done := make(chan bool)
signal.Notify(sig, os.Interrupt)
go func() {
for _ = range sig {
done <- true
}
Appendix A: Answers 151
}()
<-done
}
Chapter 8: Packages
Problem 1: Create a package with 3 source files and another
doc.go for documentation. The package should provide functions
to calculate areas for circle, rectangle, and triangle.
Solution:
circle.go:
package shape
rectangle.go:
package shape
triangle.go:
package shape
doc.go:
Chapter 9: Input/Output
Solution:
package main
import "fmt"
func main() {
c1 := Complex{Real: 2.3, Imaginary: 5}
fmt.Println(c1)
}
Appendix A: Answers 153
Problem 1: Write a test case program to fail the test and not
continue with the remaining tests.
Solution:
package main
import "testing"
$ go doc Circle
type Circle struct {
Radius float64
}
Circle represents a circle shape
$ go doc Circle.Area
func (c Circle) Area() float64
Area return the area of a circle
Further Readings
[1] Drew Neil, Practical Vim: Edit Text at the Speed of Thought,The
Pragmatic Bookshelf, Raleigh, 2012.
[2] Erich Gamma, Ralph Johnson, John Vlissides, and Richard Helm,
Design Patterns: Elements of Reusable Object-Oriented Software,
Addison-Wesley Professional, Massachusetts, 1994.
[3] C. A. R. Hoare, Communicating Sequential Processes, Prentice
Hall International, 1985.
[4] Kent Beck and Cynthia Andres, Extreme Programming
Explained: Embrace Change (XP Series), Addison-Wesley
Professional, Massachusetts, 2nd edition, 2004.
[5] Scott Chacon and Ben Straub, Pro Git, Apress, New York 2014.
155
156
Index
_, 57 inline statement, 33
input
array, 58 flag, 118
scanf, 116
blank identifier, 57 input/output, 113
break install
label, 35 linux, 3
loop, 34 windows, 4
switch, 41 interface, 85
empty, 87
channel, 94
buffered, 98 lint, 139
command line, 5, 113
comment, 16 map, 25, 63
constant, 54 method, 80, 81
continue, 36 pointer receiver, 88
label, 36 module, 108
defer, 43 operators, 22
157
return, 76 env, 134
named parameter, 77 format, 139
help, 133
select, 97 test, 137
slice, 23, 59 version, 134
slide vet, 141
append, 62 type
string alias, 109
format, 119 assertion, 89
struct, 67 built-in, 13
embedding, 67 custom, 66
switch, 39 type keyword, 66
multiple cases, 41
type, 90 var keyword, 49
without expression, 42 variable, 14
sync package, Once, 100 variadic function, 77
version, 134
test, 125
tool waitgroup, 96
build, 136
doc, 141 zero value, 49
158
Colophon
The author typeset the print version of this book in Dejavu and
Source Code Pro typefaces using the TEX system. LATEX macros and
XƎTEX extensions were used. Many other TEX packages were also
used in the preparation of this book. The book uses two variants
of the Dejavu typeface: DejaVuSerif and DejaVuSans. It also uses
SourceCodePro for monospaced text.
The picture used on the book cover is taken from Wikimedia
Commons. The original photo is by Mykl Roventine and it is licensed
under the Creative Commons CC BY 2.0 license.
The typefaces used in the cover design include Cabin,
CabinCondensed, and DejaVuSans.
Without the contributions of all the developers who work on free
and open source projects, it would not be possible to publish a book
like this. Thank you to all of these contributors for their hard work
and dedication.
The author would be very grateful for your reviews on Amazon and
other websites. When you tweet about the book, please include
the hashtag #essentialsofgo. Your blog posts, videos, or anything
else you create about this book will help to spread the word. Your
reviews and social media posts can help other people find the book
and learn about Go programming.
Please kindly send your feedback and suggestions to the following
email address: baiju.m.mail@gmail.com. You have the option to
create issues in the GitHub repository at: https://github.com/
baijum/essential-go. Additionally, you can propose changes by
creating pull requests.
INDEX
3. As you study the book with a critical mind, you are enhancing
your Go knowledge. The learning is more intense than reading
a book casually.
10. Did you notice any awkward use of the English language? (You
can ignore minor language issues.)
11. How can this book be made more interesting?