What does Go look like?
Let’s take our first look at some Go code. This code randomly prints a message to the console from a pre-defined list of messages:
package main // Import extra functionality from packages import ( "errors" "fmt" "log" "math/rand" "strconv" "time" )// Taken from: https://en.wiktionary.org/wiki/Hello_World#Translations var helloList = []string{ "Hello, world", "Καλημέρα κόσμε", "こんにちは世界", "سلام دنیا", "Привет, мир", }
The main()
function is defined as follows:
func main() { // Seed random number generator using the current time rand.NewSource(time.Now().UnixNano()) // Generate a random number in the range of out list index := rand.Intn(len(helloList)) // Call a function and receive multiple return values msg, err := hello(index) // Handle any errors if err != nil { log.Fatal(err) } // Print our message to the console fmt.Println(msg) }
Let’s consider the hello()
function:
func hello(index int) (string, error) { if index < 0 || index > len(helloList)-1 { // Create an error, convert the int type to a string return "", errors.New("out of range: " + strconv.Itoa(index)) } return helloList[index], nil }
Now, let’s step through this code piece by piece.
At the top of our script is the following:
package main
This code is our package declaration. All Go files must start with one of these. If you want to run the code directly, you’ll need to name it main
. If you don’t name it main
, then you can use it as a library and import it into other Go code. When creating an importable package, you can give it any name. All Go files in the same directory are considered part of the same package, which means all the files must have the same package name.
In the following code, we’re importing code from packages:
// Import extra functionality from packages import ( "errors" "fmt" "log" "math/rand" "strconv" "time" )
In this example, the packages are all from Go’s standard library. Go’s standard library is very high-quality and comprehensive. It’s strongly recommended that you maximize your use of it. You can tell if a package isn’t from the standard library because it’ll look like a URL – for example, github.com/fatih/color
.
Go has a module system that makes using external packages easy. To use a new module, add it to your import path. Go will automatically download it for you the next time you build code.
Imports only apply to the file they’re declared in, which means you must declare the same imports over and over in the same package and project. But fear not – you don’t need to do this by hand. There are many tools and Go editors that automatically add and remove the imports for you:
// Taken from: https://en.wiktionary.org/wiki/Hello_World#Translations var helloList = []string{ "Hello, world", "Καλημέρα κόσμε", "こんにちは世界", "سلام دنیا", "Привет, мир", }
Here, we’re declaring a global variable, which is a list of strings, and initializing it with data. The text or strings in Go support multi-byte UFT-8 encoding, making them safe for any language. The type of list we’re using here is called a slice. There are three types of lists in Go: slices, arrays, and maps. All three are collections of keys and values, where you use the key to get a value from the collection. Slice and array collections use a number as the key. The first key is always 0 in slices and arrays. Also, in slices and arrays, the numbers are contiguous, which means there is never a break in the sequence of numbers. With the map
type, you get to choose the key
type. You use this when you want to use some other data to look up the value in the map. For example, you could use a book’s ISBN to look up its title and author:
func main() { … }
Here, we’re declaring a function. A function is some code that runs when called. You can pass data in the form of one or more variables to a function and optionally receive one or more variables back from it. The main()
function in Go is special. The main()
function is the entry point of your Go code. There may only be one main()
function within the main
package. When your code runs, Go automatically calls main
to get things started:
// Seed random number generator using the current time rand.Seed(time.Now().UnixNano()) // Generate a random number in the range of out list index := rand.Intn(len(helloList))
In the preceding code, we are generating a random number. The first thing we need to do is ensure it’s a good random number; to do that, we must seed the random number generator. We seed it using the current time formatted to a Unix timestamp with nanoseconds. To get the time, we call the Now
function in the time
package. The Now
function returns a struct type variable. Structs are a collection of properties and functions, a little like objects in other languages. In this case, we are calling the UnixNano
function on that struct straight away. The UnixNano
function returns a variable of the int64
type, which is a 64-bit integer or, more simply, a number. This number is passed into rand.Seed
. The rand.Seed
function accepts an int64
variable as its input. Note that the type of the variable from time.UnixNano
and rand.Seed
must be the same. With that, we’ve successfully seeded the random number generator.
What we want is a number we can use to get a random message. We’ll use rand.Intn
for this job. This function gives us a random number between 0 and 1, minus the number we pass in. This may sound a bit strange, but it works out perfectly for what we’re trying to do. This is because our list is a slice where the keys start from 0 and increment by 1 for each value. This means the last index is 1 less than the length of the slice.
To show you what this means, here is some simple code:
package main import ( "fmt" ) func main() { helloList := []string{ "Hello, world", "Καλημέρα κόσμε", "こんにちは世界", "سلام دنیا", "Привет, мир", } fmt.Println(len(helloList)) fmt.Println(helloList[len(helloList)-1]) fmt.Println(helloList[len(helloList)]) }
This code prints the length of the list and then uses that length to print the last element. To do that, we must subtract 1; otherwise, we’d get an error, which is what the last line causes:
Figure 1.1: Output displaying an error
Once we’ve generated our random number, we assign it to a variable. We do this with the short variable declaration seen with the :=
notation, which is a very popular shortcut in Go within a function. It tells the compiler to go ahead and assign that value to the variable and select the appropriate type for that value implicitly. This shortcut is one of the many things that makes Go feel like a dynamically typed language:
// Call a function and receive multiple return values msg, err := hello(index)
Then, we use that variable to call a function named hello
. We’ll look at hello
in just a moment. The important thing to note is that we’re receiving two values back from the function and we’re able to assign them to two new variables, msg
and err
, using the :=
notation and with err
as the second value:
func hello(index int) (string, error) { … }
This code is the definition of the hello
function; we’re not showing the body for now. A function acts as a unit of logic that’s called when and as often as is needed. When calling a function, the code that calls it stops running and waits for the function to finish running. Functions are a great tool for keeping your code organized and understandable. In the signature of hello
, we’ve defined that it accepts a single int
value and that it returns a string
value and an error
value. Having error
as your last return value is a very common thing to have in Go. The code between {}
is the body of the function. The following code is what’s run when the function’s called:
if index < 0 || index > len(helloList)-1 { // Create an error, convert the int type to a string return "", errors.New("out of range: " + strconv.Itoa(index)) } return helloList[index], nil
Here, we are inside the function; the first line of the body is an if
statement. An if
statement runs the code inside its {}
if its Boolean expression is true. The Boolean expression is the logic between if
and {
. In this case, we’re testing to see if the passed index
variable is less than 0 or greater than the largest possible slice index key.
If the Boolean expression were to be true, then our code would return an empty string
and an error
value. At this point, the function would stop running, and the code that called the function would continue to run. If the Boolean expression were not true, its code would be skipped over, and our function would return a value from helloList
and nil
. In Go, nil
represents something with no value and no type:
// Handle any errors if err != nil { log.Fatal(err) }
After we’ve run hello
, the first thing we need to do is check if it ran successfully. We can do this by checking the error
value stored in err
. If err
is not equal to nil
, then we know we have an error. You will see checks on whether err
is not equal to nil
as opposed to checks on whether err
is equal to nil
, as this simplifies the checks and logic for the code base. In the case of an error, we call log.Fatal
, which writes out a logging message and kills our app. Once the app’s been killed, no more code runs:
// Print our message to the console fmt.Println(msg)
If there is no error, then we know that hello
ran successfully and that the value of msg
can be trusted to hold a valid value. The final thing we need to do is print the message to the screen via the Terminal.
Here’s how that looks:
Figure 1.2: Output displaying valid values
In this simple Go program, we’ve been able to cover a lot of key concepts that we’ll explore in full in the coming chapters.
Exercise 1.01 – using variables, packages, and functions to print stars
In this exercise, we’ll use some of what we learned about in the preceding example to print a random number, between 1 and 5, of stars (*
) to the console. This exercise will give you a feel of what working with Go is like and some practice with using the features of Go we’ll need going forward. Let’s get started:
- Create a new folder and add a
main.go
file to it. - In
main.go
, add themain
package name to the top of the file:package main
- Now, add the imports we’ll use in this file:
import ( "fmt" "math/rand" "strings" "time" )
- Create a
main()
function:func main() {
- Seed the random number generator:
rand.Seed(time.Now().UnixNano())
- Generate a random number between 0 and then add 1 to get a number between 1 and 5:
r := rand.Intn(5) + 1
- Use the string repeater to create a string with the number of stars we need:
stars := strings.Repeat("*", r)
- Print the string with the stars to the console with a new line character at the end and close the
main()
function:fmt.Println(stars) }
- Save the file. Then, in the new folder, run the following:
go run .
The following is the output:
Figure 1.3: Output displaying stars
In this exercise, we created a runnable Go program by defining the main
package with a main()
function in it. We used the standard library by adding imports to packages. Those packages helped us generate a random number, repeat strings, and write to the console.
Activity 1.01 – defining and printing
In this activity, we are going to create a medical form for a doctor’s office to capture a patient’s name, age, and whether they have a peanut allergy:
- Create a variable for the following:
- First name as a string.
- Family name as a string.
- Age as an
int
value. - Peanut allergy as a
bool
value.
- Ensure they have an initial value.
- Print the values to the console.
The following is the expected output:
Figure 1.4: Expected output after assigning the variables
Note
The solution to all activities in this chapter can be found in the GitHub repository here: https://github.com/PacktPublishing/Go-Programming-From- Beginner-to-Professional-Second-Edition-/tree/main/Chapter01
Next, we’ll start going into detail about what we’ve covered so far, so don’t worry if you are confused or have any questions about what you’ve seen so far.