Chapter 1. Getting Started Recipes
1.0 Introduction
Let’s start easy first. In this chapter, you’ll go through the essential recipes to begin coding in Go, installing Go, and then writing some simple Go code. You will go through the fundamentals, from using external libraries and handling errors to simple testing and logging events. Some of these recipes are intentionally concise—more detail about them in later chapters. If you’re already familiar with the basics of Go, you can skip this chapter altogether.
1.1 Installing Go
Solution
Go to the Go website and download the latest, greatest version of Go. Then follow the installation instructions to install Go.
Discussion
First, you need to go to the Go download site. You can choose the correct version according to your operating system and hardware and download the right package.
There are a couple of ways to install Go—you can either install the prebuilt binaries for your platform or compile it from the source. Most of the time, there is no need to compile it from the source unless you can’t find the appropriate prebuilt binaries for your operating system. Even then, you can usually use your operating system’s package manager to install Go instead.
MacOS
Open the downloaded package file and follow the prompts to install. Go will be installed at /usr/local/go, and your PATH
environment variable should also have /usr/local/go/bin
added to it.
You can also choose to install using Homebrew, which is usually a version or two behind. To do this, run this from the command line:
$ brew update && brew install golang
You can set up the PATH
later as you like.
Linux
Extract the downloaded archive file into /usr/local, which should create a go
directory. For example, run this from the command line (replace the Go version as necessary):
$ rm -rf /usr/local/go && tar -C /usr/local -xzf go1.20.1.linux-amd64.tar.gz
You can add /usr/local/go/bin into your PATH
as needed by adding this line to your $HOME/.profile:
export PATH=$PATH:/usr/local/go/bin
The changes will be made the next time you log in, or if you want it to take effect immediately, run source
on your profile file:
$ source $HOME/.profile
Build from source
You shouldn’t build from source unless you cannot find the prebuilt binaries for your operating system. However, if you need to, extract the source files to an appropriate directory first, then run these commands from the command line:
$ cd src $ ./all.bash
If you’re building in Windows, use all.bat
instead. The assumption here is that the compilers are already in place. If not, and if you need a deeper dive into installing Go, go to the Installing Go site for details.
If all goes well, you should see something like this:
ALL TESTS PASSED --- Installed Go for linux/amd64 in /home/you/go. Installed commands in /home/you/go/bin. *** You need to add /home/you/go/bin to your $PATH. ***
To check if you have installed Go, you can run the Go tool with the version
option to see the installed version:
% go version
If you have correctly installed Go, you should see something like this:
go version go1.20.1 darwin/amd64
1.2 Playing Around with Go
Solution
Use the Go Playground to run your Go code.
Discussion
The Go Playground runs on Google’s servers, and you can run your program inside a sandbox. Go to the Go Playground URL on your browser. This online environment lets you play with Go code, running the latest Go version (you can switch to a different version). The same web page returns output but only supports standard output and standard error (see Figure 1-1).
In a pinch, the Go Playground is a good option to test some Go code. You can also use the Go Playground to share executable code directly.
1.3 Writing a Hello World Program
Solution
Write the Hello World code in Go, build, and run it.
Discussion
Here’s a straightforward Hello World program. Put this in a file named hello.go, in a directory named hello
:
package
main
import
"fmt"
func
main
()
{
fmt
.
Println
(
"Hello, World!"
)
}
The first line defines the package this code runs in. Functions are grouped in packages, and code files in the same packages are all in the same directory. The main
package is special because it tells Go this should be compiled as an executable program. We also import the fmt
package, which is part of the standard library.
The main
package must have a main
function, which is where the execution of the program starts. In the body of the main
function, we use the Println
function in the fmt
package to print out the string “Hello, World!” The fmt
package is part of the Go standard library. Go has a pretty good standard library that covers most of what you will typically need. Visit Go’s Standard library to find out what the standard library has.
You can run this program immediately by running it from the command line:
$ go run hello.go
You should see this on your screen:
Hello, World!
You can also compile it into an executable file by running this command:
$ go build
This will create an executable file named hello (macOS or Linux) or hello.exe (Windows) in the same directory. The name hello follows the name of the directory it’s in. You can change the output name with this command:
$ go build -o helloworld
This will create the executable file named helloworld (macOS or Linux) or helloworld.exe (Windows).
1.4 Using an External Package
Discussion
Let’s say you want to display the size of a file. You get the exact file size, but it’s a relatively large number that is not intuitive to users. You want to easily display the file size without doing much of the mathematics yourself.
You searched the internet and found this interesting third-party, open source package at https://github.com/dustin/go-humanize, and it does all that you want. How can you include it and use the functions in the package?
You can do this just like importing a standard library, but instead of using the package name, use the package location. Normally you’d expect the name of the package to be go-humanize
. However, the package name used in the code itself is humanize
. This is because the package name, as defined by the author of this package, is humanize
:
package
main
import
(
"fmt"
"github.com/dustin/go-humanize"
)
func
main
()
{
var
number
uint64
=
123456789
fmt
.
Println
(
"Size of file is"
,
humanize
.
Bytes
(
number
))
}
In many cases, the last directory of the location is the name of the package, but it’s not always necessarily so. You can even change the way you call functions in the external package:
package
main
import
(
"fmt"
human
"github.com/dustin/go-humanize"
)
func
main
()
{
var
number
uint64
=
123456789
fmt
.
Println
(
"Size of file is"
,
human
.
Bytes
(
number
))
}
Notice that you now use the name human
when calling the functions. Why does Go allow this? It’s because there might be conflicting package names since Go doesn’t control how the package is named, nor does it have a centralized repository of packages.
If you try to run this directly, assuming the source code is in a file named human.go:
$ go run human.go
you will see this error message:
human.go:6:2: no required module provides package github.com/dustin/go-humanize: go.mod file not found in the current directory or any parent directory; see 'go help modules'
This is because Go doesn’t know where to find the third-party package (unlike the standard library packages); you must tell it. To do this, you first need to create a Go module:
$ go mod init github.com/sausheong/humanize
This creates a Go module with the module path github.com/sausheong/humanize, specified in a go.mod file. This file provides Go with information on the various third-party packages to include. Then you can get the go-humanize
package using the go
tool again:
$ go get github.com/dustin/go-humanize
This will add the third-party package to the module. To clean up, you can run the following:
$ go mod tidy
This will clean up the go.mod file. You will get back to Go modules in Chapter 2.
1.5 Handling Errors
Solution
Check if a function returns an error and handle it accordingly.
Discussion
Error handling in Go is important. Go is designed such that you need to check for errors explicitly. Functions that could go wrong will return a built-in type called error
.
Functions that convert a string to a number (like ParseFloat
and ParseInt
) can get into trouble because the string might not be a number, so it will always return an error. For example, in the strconv
package, functions that convert a number to a string (like FormatFloat
and FormatInt
) do not return errors. This is because you are forced to pass in a number, and whatever you pass in will be converted into a string.
Take a look at the following code:
func
main
()
{
str
:=
"123456789"
num
,
err
:=
strconv
.
ParseInt
(
str
,
10
,
64
)
if
err
!=
nil
{
panic
(
err
)
}
fmt
.
Println
(
"Number is"
,
num
)
}
The ParseInt
function takes in a string (and some other parameters) and returns a number num
and an error err
. You should inspect the err
to see if the ParseInt
function returns anything. If there is an error, you can handle it as you prefer. In this example, you panic
, which exits the program.
If all goes well, this is what you should see:
Number is 123456789
If you change str
to "abcdefg"
, you will get this:
panic: strconv.ParseInt: parsing "abcdefghi": invalid syntax goroutine 1 [running]: main.main() /Users/sausheong/work/src/github.com/sausheong/gocookbook/ch01_general/ main.go +0xae exit status 2
Of course, you can handle it differently or even ignore it if you want. You’ll get in-depth with error handling in Chapter 3.
1.6 Logging Events
Discussion
Logging events during code execution gives you a good view of how the code is doing during execution. This is important, especially during long-running programs. Logs help to determine issues with the execution and also the state of the execution. Here is a simple example used earlier:
package
main
import
(
"fmt"
"log"
"strconv"
)
func
main
()
{
str
:=
"abcdefghi"
num
,
err
:=
strconv
.
ParseInt
(
str
,
10
,
64
)
if
err
!=
nil
{
log
.
Fatalln
(
"Cannot parse string:"
,
err
)
}
fmt
.
Println
(
"Number is"
,
num
)
}
When you encounter an error being returned from calling the strconv.ParseInt
function, you call log.Fatalln
, which is equivalent to logging the output to the screen and exiting the application. As you can see, logging to the screen also adds the date and time the event occurred:
021/11/18 09:19:35 Cannot parse string: strconv.ParseInt: parsing "abcdefghi": invalid syntax exit status 1
By default, the log goes to standard out, which means it will print to the terminal screen. You can easily convert it to log to a file instead, or even multiple files. More about that in Chapter 4.
1.7 Testing Your Code
Solution
Use Go’s built-in testing tool to do functional tests.
Discussion
Go has a useful built-in testing tool that makes testing easier since you don’t need to add another third-party library. You’ll convert the previous code to a function while leaving your main
function free:
func
main
()
{
}
func
conv
(
str
string
)
(
num
int64
,
err
error
)
{
num
,
err
=
strconv
.
ParseInt
(
str
,
10
,
64
)
return
}
You’ll be doing some testing on this function. To do this, create a file that ends with _test.go in the same directory. In this case, create a conv_test.go file.
In this file, you can write the various test cases you want. Each test case can correspond to a function that starts with Test
and takes in a single parameter of type testing.T
.
You can add as many test cases as you want across all multiple test files, as long as they all end with _test.go:
package
main
import
"testing"
func
TestConv
(
t
*
testing
.
T
)
{
num
,
err
:=
conv
(
"123456789"
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
if
num
!=
123456789
{
t
.
Fatal
(
"Number don't match"
)
}
}
func
TestFailConv
(
t
*
testing
.
T
)
{
_
,
err
:=
conv
(
""
)
if
err
==
nil
{
t
.
Fatal
(
err
)
}
}
Within the test functions, you call the conv
function that you wanted to test, passing it whatever test data you want. If the function returns an error or the returned value doesn’t match what you expect, you call the Fatal
function, which logs a message and then ends the execution of the test.
Try it: run this from the command line. The flag -v
is to increase its verbosity so you can see how many test cases are executed and passed:
$ go test -v
This is what you see:
=== RUN TestConv --- PASS: TestConv (0.00s) === RUN TestFailConv --- PASS: TestFailConv (0.00s) PASS ok github.com/sausheong/gocookbook/ch01_general
As you can see, all your cases pass. Now make a small change in your conv
function:
func
conv
(
str
string
)
(
num
int64
,
err
error
)
{
num
,
err
=
strconv
.
ParseInt
(
str
,
2
,
64
)
return
}
Instead of parsing the number as base 10, you use base 2. Rerun the test:
=== RUN TestConv general_test.go:8: strconv.ParseInt: parsing "123456789": invalid syntax --- FAIL: TestConv (0.00s) === RUN TestFailConv --- PASS: TestFailConv (0.00s) FAIL exit status 1 FAIL github.com/sausheong/gocookbook/ch01_general
You see that the TestConv
test case failed because it no longer returns the expected number. However, the second test case passes because it tests for a failure and it encountered it.
Testing is covered more extensively in Chapter 18.
Get Go Cookbook now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.