Search icon CANCEL
Subscription
0
Cart icon
Cart
Close icon
You have no products in your basket yet
Save more on your purchases!
Savings automatically calculated. No voucher code required
Arrow left icon
All Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Newsletters
Free Learning
Arrow right icon
Go Programming - From Beginner to Professional - Second Edition
Go Programming - From Beginner to Professional - Second Edition

Go Programming - From Beginner to Professional: Learn everything you need to build modern software using Go, Second Edition

By Ms. Samantha Coyle
€26.99 €17.99
Book Mar 2024 680 pages 2nd Edition
eBook
€26.99 €17.99
Print
€33.99 €24.99
Subscription
Free Trial
Renews at €14.99p/m
eBook
€26.99 €17.99
Print
€33.99 €24.99
Subscription
Free Trial
Renews at €14.99p/m

What do you get with eBook?

Product feature icon Instant access to your Digital eBook purchase
Product feature icon Download this book in EPUB and PDF formats
Product feature icon AI Assistant (beta) to help accelerate your learning
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
Table of content icon View table of contents Preview book icon Preview Book

Go Programming - From Beginner to Professional - Second Edition

Variables and Operators

Overview

In this chapter, you will be introduced to various features of Go and gain a basic understanding of what Go code looks like. You will also be provided with a deep understanding of how variables work and will perform exercises and activities to get hands-on and get going.

By the end of this chapter, you will be able to use variables, packages, and functions in Go. You will also know how to change variable values in Go. Later in this chapter, you will use operators with numbers and design functions using pointers.

Technical requirements

For this chapter, you'll require Go version 1.21 or higher. The code for this chapter can be found at: https://github.com/PacktPublishing/Go-Programming-From-Beginner-to-Professional-Second-Edition-/tree/main/Chapter01.

Introduction to Go

Go (or Golang, as it’s often called) is a programming language that’s popular with developers because of how rewarding it is to use to develop software. It’s also popular with companies because teams of all sizes can be productive with it. Go has also earned a reputation for consistently delivering software with exceptionally high performance.

Go has an impressive pedigree since it was created by a team from Google with a long history of building great programming languages and operating systems. They created a language that has the feel of a dynamic language such as JavaScript or PHP but with the performance and efficiency of strongly typed languages such as C++ and Java. They wanted a language that was engaging for the programmer but practical in projects with hundreds of developers.

Go is packed with interesting and unique features, such as being compliant with memory safety and channel-based concurrency. We’ll explore these features in this chapter. By doing so, you’ll see that their unique implementation within Go is what makes Go truly special.

Go is written in text files that are then compiled down to machine code and packaged into a single, standalone executable file. The executable is self-contained, with nothing needed to be installed first to allow it to run. Having a single file makes deploying and distributing Go software hassle-free. When compiling, you can pick one of several target operating systems, including – but not limited to – Windows, Linux, macOS, and Android. With Go, you write your code once and run it anywhere. Complied languages fell out of favor because programmers hated long waits for their code to compile. The Go team knew this and built a lightning-fast compiler that remains fast as projects grow.

Go has a statically typed and type-safe memory model with a garbage collector that automates memory management. This combination protects developers from creating many of the most common bugs and security flaws found in software while still providing excellent performance and efficiency. Dynamically typed languages such as Ruby and Python have become popular in part because programmers felt they could be more productive if they didn’t have to worry about types and memory. The downside of these languages is that they give up performance and memory efficiency and can be more prone to type-mismatch bugs. Go has the same levels of productivity as dynamically typed languages while not giving up performance and efficiency.

A massive shift in computer performance has taken place. Going fast now means you need to be able to do as much work parallel or concurrently as possible. This change is due to the design of modern CPUs, which emphasize more cores over high clock speed. None of the currently popular programming languages have been designed to take advantage of this fact, which makes writing parallel and concurrent code in them error-prone. Go is designed to take advantage of multiple CPU cores, and it removes all the frustration and bug-filled code. Go is designed to allow any developer to easily and safely write parallel and concurrent code that enables them to take advantage of modern multicore CPUs and cloud computing – unlocking high-performance processing and massive scalability without the drama.

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

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

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:

  1. Create a new folder and add a main.go file to it.
  2. In main.go, add the main package name to the top of the file:
    package main
  3. Now, add the imports we’ll use in this file:
    import (
      "fmt"
      "math/rand"
      "strings"
      "time"
    )
  4. Create a main() function:
    func main() {
  5. Seed the random number generator:
      rand.Seed(time.Now().UnixNano())
  6. Generate a random number between 0 and then add 1 to get a number between 1 and 5:
      r := rand.Intn(5) + 1
  7. Use the string repeater to create a string with the number of stars we need:
      stars := strings.Repeat("*", r)
  8. 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)
    }
  9. Save the file. Then, in the new folder, run the following:
    go run .

The following is the output:

Figure 1.3: Output displaying stars

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:

  1. Create a variable for the following:
    1. First name as a string.
    2. Family name as a string.
    3. Age as an int value.
    4. Peanut allergy as a bool value.
  2. Ensure they have an initial value.
  3. Print the values to the console.

The following is the expected output:

Figure 1.4: Expected output after assigning the variables

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.

Declaring variables

Now that you’ve had a glimpse of Go and completed your first exercise, we’re going to dive deep. Our first stop on our journey is variables.

A variable holds data for you temporarily so that you can work with it. When you declare a variable, it needs four things: a statement that you are declaring a variable, a name for the variable, the type of data it can hold, and an initial value for it. Fortunately, some of the parts are optional, but that also means there’s more than one way of defining a variable.

Let’s cover all the ways you can declare a variable.

Declaring a variable using var

Using var is the foundational way to declare a variable. Every other way we’ll cover is a variation of this approach, typically by omitting parts of this definition. A full var definition with everything in place looks like this:

var foo string = "bar"

The key parts are var, foo, string, and = "bar":

  • var is our declaration that we are defining a variable
  • foo is the name of the variable
  • string is the type of the variable
  • = "bar" is its initial value

Exercise 1.02 – declaring a variable using var

In this exercise, we’ll declare two variables using the full var notation. Then, we’ll print them to the console. You’ll see that you can use the var notation anywhere in your code, which isn’t true for all variable declaration notations. Let’s get started:

  1. Create a new folder and add a main.go file to it.
  2. In main.go, add the main package name to the top of the file:
    package main
  3. Add the imports:
    import (
      "fmt"
    )
  4. Declare a variable at the package-level scope. We’ll cover what scopes are in detail later:
    var foo string = "bar"
  5. Create the main() function:
    func main() {
  6. Declare another variable using var in our function:
      var baz string = "qux"
  7. Print both variables to the console:
      fmt.Println(foo, baz)
  8. Close the main() function:
    }
  9. Save the file. Then, in the new folder, run the following:
    go run .

The following is the output:

bar qux

In this example, foo is declared at the package level while baz is declared at the function level. Where a variable is declared is important because where you declare a variable also limits what notation you can use to declare it.

Next, we’ll look at another way to use the var notation.

Declaring multiple variables at once with var

We can use a single var declaration to define more than one variable using a var block or statement. Using this method is common when declaring package-level variables. The variables don’t need to be of the same type, and they can all have their own initial values. The notation looks like this:

var (
  <name1> <type1> = <value1>
  <name2> <type2> = <value2>
…
  <nameN> <typeN> = <valueN>
)

You can have multiple of these types of declarations. This is a nice way to group related variables, thereby making your code more readable. You can use this notation in functions, but it’s rare to see it used there.

Exercise 1.03 – declaring multiple variables at once with var

In this exercise, we’ll declare multiple variables using one var statement, each with a different type and initial value. Then, we’ll print the value of each variable to the console. Let’s get started:

  1. Create a new folder and add a main.go file to it.
  2. In main.go, add the main package name to the top of the file:
    package main
  3. Add the imports:
    import (
      "fmt"
      "time"
    )
  4. Start the var declaration:
    var (
  5. Define three variables:
      Debug   bool   = false
      LogLevel  string  = "info"
      startUpTime time.Time = time.Now()
  6. Close the var declaration:
    )
  7. In the main() function, print each variable to the console:
    func main() {
      fmt.Println(Debug, LogLevel, startUpTime)
    }
  8. Save the file. Then, in the new folder, run the following:
    go run .

    The following is the output:

Figure 1.5: Output displaying three variable values

Figure 1.5: Output displaying three variable values

In this exercise, we declared three variables using a single var statement. Your output will look different for the time.Time variable, but that’s correct. The format is the same, but the time itself is different.

Using the var notation like this is a good way to keep your code well organized and save you some typing.

Next, we’ll start removing some of the optional parts of the var notation.

Skipping the type or value when declaring variables

In real-world code, it’s not common to use the full var notation. There are a few cases where you need to define a package-level variable with an initial value and tightly control its type. In those cases, you need the full notation. It’ll be obvious when this is needed as you’ll have a type mismatch of some kind, so don’t worry too much about this for now. The rest of the time, you’ll remove an optional part or use the short variable declaration.

You don’t need to include both the type and the initial value when declaring a variable. You can use just one or the other; Go works out the rest. If you have a type in the declaration but no initial value, Go uses the zero value for the type you picked. We’ll talk about what a zero value is later in this book. On the other hand, if you have an initial value and no type, Go has a ruleset for how to infer the types that are needed from the literal value you use.

Exercise 1.04 – skipping the type or value when declaring variables

In this exercise, we’ll update our previous exercise so that it skips the optional initial values or type declarations from our variable declaration. Then, we’ll print the values to the console, as we did previously, to show that the result is the same. Let’s get started:

  1. Create a new folder and add a main.go file to it.
  2. In main.go, add the main package name to the top of the file:
    package main
  3. Import the packages we’ll need:
    import (
      "fmt"
      "time"
    )
  4. Start the multi-variable declaration:
    var (
  5. The bool value in the first exercise has an initial value of false. That’s a bool value’s zero value, so we’ll drop the initial value from its declaration as it is set by default:
      Debug   bool
  6. The next two variables both have a non-zero value for their type, so we’ll drop their type declaration:
      LogLevel  = "info"
      startUpTime = time.Now()
  7. Close the var declaration:
    )
  8. In the main() function, print out each variable:
    func main() {
      fmt.Println(Debug, LogLevel, startUpTime)
    }
  9. Save the file. Then, in the new folder, run the following:
    go run .

    The following is the output:

Figure 1.6: Output displaying variable values despite not mentioning the type while declaring the variables

Figure 1.6: Output displaying variable values despite not mentioning the type while declaring the variables

In this exercise, we were able to update the previous code so that it uses a much more compact variable declaration. Declaring variables is something you’ll have to do a lot, and not having to use the notation makes for a better experience when writing code.

Next, we’ll look at a situation where you can’t skip any of the parts.

Type inference gone wrong

There are times when you’ll need to use all the parts of the declaration – for example, when Go isn’t able to guess the correct type you need. Let’s take a look at an example of this:

package main
import "math/rand"
func main() {
  var seed = 1234456789
  rand.NewSource(seed)
}

The following is the output:

Figure 1.7: Output showing an error

Figure 1.7: Output showing an error

The issue here is that rand.NewSource requires a variable of the int64 type. Go’s type inference rules interoperate a whole number, such as the one we used as an int value. We’ll look at the difference between them in more detail later in this book. To resolve this, we will add int64 type to the declaration. Here’s how that looks:

package main
import "math/rand"
func main() {
  var seed int64 = 1234456789
  rand.NewSource(seed)
}

Next, we’ll look at an even quicker way to declare variables.

Short variable declaration

When declaring variables in functions and functions only, we can use the := shorthand. This shorthand allows us to make our declarations even shorter. It does this by allowing us to not have to use the var keyword and by always inferring the type from a required initial value.

Exercise 1.05 – implementing a short variable declaration

In this exercise, we’ll update our previous exercise so that it uses a short variable declaration. Since you can only use a short variable declaration in a function, we’ll move our variable out of the package scope. Where before Debug had a type but no initial value, we’ll switch it back so that it has an initial value since that’s required when using a short variable declaration. Finally, we’ll print it to the console. Let’s get started:

  1. Create a new folder and add a main.go file to it.
  2. In main.go, add the main package name to the top of the file:
    package main
  3. Import the packages we’ll need:
    import (
      "fmt"
      "time"
    )
  4. Create the main() function:
    func main() {
  5. Declare each variable using the short variable declaration notation:
      Debug := false
      LogLevel := "info"
      startUpTime := time.Now()
  6. Print the variables to the console:
      fmt.Println(Debug, LogLevel, startUpTime)
    }
  7. Save the file. Then, in the new folder, run the following:
    go run .

The following is the output:

Figure 1.8: Output displaying the variable values that were printed after using short variable declaration notation

Figure 1.8: Output displaying the variable values that were printed after using short variable declaration notation

In this exercise, we updated our previous code to use a very compact way to declare variables when we have an initial value to use.

The := shorthand is very popular with Go developers and the most common way in which variables get defined in real-world Go code. Developers like how it makes their code concise and compact while still being clear as to what’s happening.

Another shortcut is declaring multiple variables on the same line.

Declaring multiple variables with a short variable declaration

It’s possible to declare multiple variables at the same time using a short variable declaration. They must all be on the same line, and each variable must have a corresponding initial value. The notation looks like <var1>, <var2>, …, <varN> := <val1>, <val2>, …, <valN>. The variable names are on the left-hand side of:=, separated by ,. The initial values are on the right-hand side of:= again, each separated by ,. The leftmost variable name gets the leftmost value. There must be an equal number of names and values.

Here is an example that uses our previous exercise’s code:

package main
import (
  "fmt"
  "time"
)
func main() {
  Debug, LogLevel, startUpTime := false, "info", time.Now()
  fmt.Println(Debug, LogLevel, startUpTime)
}

The following is the output:

Figure 1.9: Example output displaying the variable values for the program with a variable declaring function

Figure 1.9: Example output displaying the variable values for the program with a variable declaring function

Sometimes, you do see real-world code like this. It’s a little hard to read, so it’s not common to see it in terms of literal values. This doesn’t mean this isn’t common, though – it’s very common when calling functions that return multiple values. We’ll cover this in detail when we look at functions later in this book.

Exercise 1.06 – declaring multiple variables from a function

In this exercise, we’ll call a function that returns multiple values, and we’ll assign each value to a new variable. Then, we’ll print the values to the console. Let’s get started:

  1. Create a new folder and add a main.go file to it.
  2. In main.go, add the main package name to the top of the file:
    package main
  3. Import the packages we’ll need:
    import (
      "fmt"
      "time"
    )
  4. Create a function that returns three values:
    func getConfig() (bool, string, time.Time) {
  5. In the function, return three literal values, each separated by ,:
      return false, "info", time.Now()
  6. Close the function:
    }
  7. Create the main() function:
    func main() {
  8. Using a short variable declaration, capture the values that were returned from the function’s three new variables:
      Debug, LogLevel, startUpTime := getConfig()
  9. Print the three variables to the console:
      fmt.Println(Debug, LogLevel, startUpTime)
  10. Close the main() function:
    }
  11. Save the file. Then, in the new folder, run the following:
    go run .

    The following is the output:

Figure 1.10: Output displaying the variable values for the program with the variable declaring function

Figure 1.10: Output displaying the variable values for the program with the variable declaring function

In this exercise, we were able to call a function that returned multiple values and capture them using a short variable declaration in one line. If we used the var notation, it would look like this:

var (
  Debug bool
  LogLevel string
  startUpTime time.Time
)
Debug, LogLevel, startUpTime = getConfig()

Short variable notation is a big part of how Go has the feel of a dynamic language.

We’re not quite done with var yet, though – it still has a useful trick up its sleeve.

Using var to declare multiple variables in one line

While it’s more common to use a short variable declaration, you can use var to define multiple variables on a single line. One limitation of this is that, when declaring the type, all the values must have the same type. If you use an initial value, then each value infers its type from the literal value so that they can differ. Here’s an example:

package main
import (
  "fmt"
  "time"
)
func getConfig() (bool, string, time.Time) {
  return false, "info", time.Now()
}
func main() {
  // Type only
  var start, middle, end float32
  fmt.Println(start, middle, end)
  // Initial value mixed type
  var name, left, right, top, bottom = "one", 1, 1.5, 2, 2.5
  fmt.Println(name, left, right, top, bottom)
  // works with functions also
  var Debug, LogLevel, startUpTime = getConfig()
  fmt.Println(Debug, LogLevel, startUpTime)
}

The following is the output:

Figure 1.11: Output displaying variable values

Figure 1.11: Output displaying variable values

Most of these are more compact when using a short variable declaration. This fact means they don’t come up in real-world code much. The exception is the same type-only example. This notation can be useful when you need many variables of the same type, and you need to control that type carefully.

Non-English variable names

Go is a UTF-8 compliant language, which means you can define variables’ names using alphabets other than the Latin alphabet that, for example, English uses. There are some limitations regarding what the name of a variable can be. The first character of the name must be a letter or _. The rest can be a mixture of letters, numbers, and _. Let’s have a look at what this looks like:

package main
import (
  "fmt"
  "time"
)
func main() {
  デバッグ := false
  日志级别 := "info"
  ይጀምሩ := time.Now()
  _A1_Μείγμα := "" 
"
  fmt.Println(デバッグ, 日志级别, ይጀምሩ, _A1_Μείγμα)
}

The following is the output:

Figure 1.12: Output showing variable values

Figure 1.12: Output showing variable values

Note

Languages and language: Not all programming languages allow you to use UTF-8 characters as variables and function names. This feature could be one of the reasons why Go has become so popular in Asian countries, particularly in China.

Changing the value of a variable

Now that we’ve defined our variables, let’s see what we can do with them. First, let’s change the value from its initial value. To do that, we’ll use a similar notation to when we set an initial value. This looks like <variable> = <value>.

Exercise 1.07 – changing the value of a variable

Follow these steps:

  1. Create a new folder and add a main.go file to it.
  2. In main.go, add the main package name to the top of the file:
    package main
  3. Import the packages we’ll need:
    import "fmt"
  4. Create the main() function:
    func main() {
  5. Declare a variable:
      offset := 5
  6. Print the variable to the console:
      fmt.Println(offset)
  7. Change the value of the variable:
      offset = 10
  8. Print it to the console again and close the main() function:
      fmt.Println(offset)
    }
  9. Save the file. Then, in the new folder, run the following:
    go run .

The following is the output before changing the variable’s value:

5
10

In this example, we’ve changed the value of offset from its initial value of 5 to 10. Anywhere you use a raw value, such as 5 and 10 in our example, you can use a variable. Here’s how that looks:

package main
import "fmt"
var defaultOffset = 10
func main() {
  offset := defaultOffset
  fmt.Println(offset)
  offset = offset + defaultOffset
  fmt.Println(offset)
}

The following is the output after changing the variable’s value:

10
20

Next, we’ll look at how we can change multiple variables in a one-line statement.

Changing multiple values at once

In the same way that you can declare multiple variables in one line, you can also change the value of more than one variable at a time. The syntax is similar, too; it looks like <var1>, <var2>, …, <varN> = <val1>, <val2>, …, <valN>.

Exercise 1.08 – changing multiple values at once

In this exercise, we’ll define some variables and use a one-line statement to change their values. Then, we’ll print their new values to the console. Let’s get started:

  1. Create a new folder and add a main.go file to it.
  2. In main.go, add the main package name to the top of the file:
    package main
  3. Import the packages we’ll need:
    import "fmt"
  4. Create the main() function:
    func main() {
  5. Declare our variables with an initial value:
      query, limit, offset := "bat", 10, 0
  6. Change each variable’s values using a one-line statement:
      query, limit, offset = "ball", offset, 20
  7. Print the values to the console and close the main() function:
      fmt.Println(query, limit, offset)
    }
  8. Save the file. Then, in the new folder, run the following:
    go run .

The following is the output showing the changed variable values using a single statement:

ball 0 20

In this exercise, we were able to change multiple variables in a single line. This approach would also work when calling functions, just as it does with a variable declaration. You need to be careful with a feature like this to ensure that, first and foremost, your code is easy to read and understand. If using a one-line statement like this makes it hard to know what the code is doing, then it’s better to take up more lines to write the code.

Next, we’ll look at what operators are and how they can be used to change your variables in interesting ways.

Operators

While variables hold the data for your application, they become truly useful when you start using them to build the logic of your software. Operators are the tools you use to work with your software’s data. With operators, you can compare data to other data – for example, you can check whether a price is too low or too high in a trading application. You can also use operators to manipulate data. For example, you can use operators to add the costs of all the items in a shopping cart to get the total price.

The following list mentions groups of operators:

  • Arithmetic operators: These are used for math-related tasks such as addition, subtraction, and multiplication.
  • Comparison operators: These are used to compare two values; for example, whether they are equal, not equal, less than, or greater than each other.
  • Logical operators: These are used with Boolean values to see whether they are both true, only one is true, or whether a bool value is false.
  • Address operators: We’ll cover these in detail soon when we look at pointers. These are used to work with them.
  • Receive operators: These are used when working with Go channels. We’ll cover this later in this book.

Exercise 1.09 – using operators with numbers

In this exercise, we are going to simulate a restaurant bill. To build our simulation, we’ll need to use mathematic and comparison operators. We’ll start by exploring all the major uses for operators.

In our simulation, we’ll sum everything together and work out the tip based on a percentage. Then, we’ll use a comparison operator to see whether the customer gets a reward. Let’s get started:

Note

We have considered the US dollar as the currency for this exercise. You may consider any currency of your choice; the main focus here is the operations.

  1. Create a new folder and add a main.go file to it.
  2. In main.go, add the main package name to the top of the file:
    package main
  3. Import the packages you’ll need:
    import "fmt"
  4. Create the main() function:
    func main() {
  5. Create a variable to hold the total. For this item on the bill, the customer purchased two items that cost 13 USD. We must use * to do the multiplication. Then, we must print a subtotal:
      // Main course
      var total float64 = 2 * 13
      fmt.Println("Sub :", total)
  6. Here, they purchased four items that cost 2.25 USD. We must use multiplication to get the total of these items, use + to add it to the previous total value, and then assign that back to the total:
      // Drinks
      total = total + (4 * 2.25)
      fmt.Println("Sub :", total)
  7. This customer is getting a discount of 5 USD. Here, we use to subtract 5 USD from the total:
      // Discount
      total = total - 5
      fmt.Println("Sub :", total)
  8. Then, we use multiplication to calculate a 10% tip:
      // 10% Tip
      tip := total * 0.1
      fmt.Println("Tip :", tip)
  9. Finally, we add the tip to the total:
      total = total + tip
      fmt.Println("Total:", total)
  10. The bill will be split between two people. Use / to divide the total into two parts:
      // Split bill
      split := total / 2
      fmt.Println("Split:", split)
  11. Here, we’ll calculate whether the customer gets a reward. First, we’ll set visitCount and then add 1 USD to this visit:
      // Reward every 5th visit
      visitCount := 24
      visitCount = visitCount + 1
  12. Then, we’ll use % to give us any remainder after dividing visitCount by 5 USD:
      remainder := visitCount % 5
  13. The customer gets a reward on every fifth visit. If the remainder is 0, then this is one of those visits. Use the == operator to check whether the remainder is 0:
      if remainder == 0 {
  14. If it is, print a message stating that they get a reward:
        fmt.Println("With this visit, you've earned a reward.")
      }
    }
  15. Save the file. Then, in the new folder, run the following:
    go run .

The following is the output:

Figure 1.13: Output of operators used with numbers

Figure 1.13: Output of operators used with numbers

In this exercise, we used the math and comparison operators with numbers. They allowed us to model a complex situation – calculating a restaurant bill. There are lots of operators and which ones you can use vary with the different types of values. For example, as well as there being an addition operator for numbers, you can use the + symbol to join strings together. Here’s this in action:

package main
import "fmt"
func main() {
  givenName := "John"
  familyName := "Smith"
  fullName := givenName + " " + familyName
  fmt.Println("Hello,", fullName)
}

The following is the output:

Hello, John Smith

For some situations, there are some shortcuts we can make with operators. We’ll go over this in the next section.

Bitwise operators

Go has all the familiar bitwise operators you’d find in programming languages. If you know what bitwise operators are, then there will be no surprises here for you. If you don’t know what bitwise operators are, don’t worry – they aren’t common in real-world code.

Shorthand operators

There are a few shorthand assignment operators when you want to perform operations on an existing value with its own value:

  • --: Reduce a number by 1
  • ++: Increase a number by 1
  • +=: Add and assign
  • -=: Subtract and assign

Exercise 1.10 – implementing shorthand operators

In this exercise, we’ll use some examples of operator shorthand to show how they can make your code more compact and easier to write. We’ll create some variables and then use shorthand to change them, printing them out as we go. Let’s get started:

  1. Create a new folder and add a main.go file to it.
  2. In main.go, add the main package name to the top of the file:
    package main
  3. Import the packages we’ll need:
    import "fmt"
  4. Create the main() function:
    func main() {
  5. Create a variable with an initial value:
      count := 5
  6. We’ll add to it and then assign the result back to itself. Then, we’ll print it out:
      count += 5
      fmt.Println(count)
  7. Increment the value by 1 and then print it out:
      count++
      fmt.Println(count)
  8. Decrement it by 1 and then print it out:
      count--
      fmt.Println(count)
  9. Subtract and assign the result back to itself. Print out the new value:
      count -= 5
      fmt.Println(count)
  10. There is also a shorthand that works with strings. Define a string:
      name := "John"
  11. Next, we’ll append another string to the end of it and then print it out:
      name += " Smith"
      fmt.Println("Hello,", name)
  12. Close the main() function:
    }
  13. Save the file. Then, in the new folder, run the following:
    go run .

The following is the output:

Figure 1.14: Output using shorthand operators

Figure 1.14: Output using shorthand operators

In this exercise, we used some shorthand operators. One set focused on modification and then assignment. This type of operation is common, and having these shortcuts makes coding more engaging. The other operators are increment and decrement. These are useful in loops when you need to step over data one at a time. These shortcuts make it clear what you’re doing to anyone who reads your code.

Next, we’ll look at comparing values to each other in detail.

Comparing values

Logic in applications is a matter of having your code make a decision. These decisions are made by comparing the values of variables to the rules you define. These rules come in the form of comparisons. We use another set of operators to make these comparisons. The result of these comparisons is always true or false. You’ll also often need to make lots of these comparisons to make a single decision. To help with that, we have logical operators.

These operators, for the most part, work with two values and always result in a Boolean value. You can only use logical operators with Boolean values. Let’s take a look at comparison operators and logical operators in more detail.

Comparison operators:

  • ==: True if two values are the same
  • !=: True if two values are not the same
  • <: True if the left value is less than the right value
  • <=: True if the left value is less or equal to the right value
  • >: True if the left value is greater than the right value
  • >=: True if the left value is greater than or equal to the right value

Logical operators:

  • &&: True if the left and right values are both true
  • ||: True if one or both the left and right values are true
  • !: This operator only works with a single value and results in true if the value is false

Exercise 1.11 – comparing values

In this exercise, we’ll use comparison and logical operators to see what Boolean results we get when testing different conditions. We are testing to see what level of membership a user has based on the number of visits they’ve had.

Our membership levels are as follows:

  • Silver: Between 10 and 20 visits inclusively
  • Gold: Between 21 and 30 visits inclusively
  • Platinum: Over 30 visits

Let’s get started:

  1. Create a new folder and add a main.go file to it.
  2. In main.go, add the main package name to the top of the file:
    package main
  3. Import the packages we’ll need:
    import "fmt"
  4. Create the main() function:
    func main() {
  5. Define our visits variable and initialize it with a value:
      visits := 15
  6. Use the equals operator to see whether this is their first visit. Then, print the result to the console:
      fmt.Println("First visit   :", visits == 1)
  7. Use the not equal operator to see whether they are a returning visitor:
      fmt.Println("Return visit  :", visits != 1)
  8. Let’s check whether they are a silver member using the following code:
      fmt.Println("Silver member :", visits >= 10 && visits < 21)
  9. Let’s check whether they are a gold member using the following code:
      fmt.Println("Gold member   :", visits > 20 && visits <= 30)
  10. Let’s check whether they are a platinum member using the following code:
      fmt.Println("Platinum member :", visits > 30)
  11. Close the main() function:
    }
  12. Save the file. Then, in the new folder, run the following:
    go run .

The following is the output:

Figure 1.15: Output displaying the comparison result

Figure 1.15: Output displaying the comparison result

In this exercise, we used comparison and logical operators to make decisions about data. You can combine these operators in an unlimited number of ways to express almost any type of logic your software needs to make.

Next, we’ll look at what happens when you don’t give a variable an initial value.

Zero values

The zero value of a variable is the empty or default value for that variable’s type. Go has a set of rules stating that the zero values are for all the core types. Let’s take a look:

Figure 1.16: Variable types and their zero values

Figure 1.16: Variable types and their zero values

There are other types, but they are all derived from these core types, so the same rules still apply.

We’ll look at the zero values of some types in the upcoming exercise.

Exercise 1.12 – zero values

In this example, we’ll define some variables without an initial value. Then, we’ll print out their values. We’re using fmt.Printf to help us in this exercise as we can get more detail about a value’s type. fmt.Printf uses a template language that allows us to transform passed values. The substitution we’re using is %#v. This transformation is a useful tool for showing a variable’s value and type. Some other common substitutions you can try are as follows:

Figure 1.17: Table on substitutions

Figure 1.17: Table on substitutions

When using fmt.Printf, you need to add the new line symbol yourself. You can do this by adding \n to the end of the string. Let’s get started:

  1. Create a new folder and add a main.go file to it.
  2. In main.go, add the main package name to the top of the file:
    package main
  3. Import the packages we’ll need:
    import (
      "fmt"
      "time"
    )
  4. Create the main() function:
    func main() {
  5. Declare and print an integer:
      var count int
      fmt.Printf("Count  : %#v \n", count)
  6. Declare and print a float value:
      var discount float64
      fmt.Printf("Discount : %#v \n", discount)
  7. Declare and print a bool value:
      var debug bool
      fmt.Printf("Debug  : %#v \n", debug)
  8. Declare and print a string value:
      var message string
      fmt.Printf("Message : %#v \n", message)
  9. Declare and print a collection of strings:
      var emails []string
      fmt.Printf("Emails : %#v \n", emails)
  10. Declare and print a struct (a type composed of other types; we will cover this later in this book):
      var startTime time.Time
      fmt.Printf("Start  : %#v \n", startTime)
  11. Close the main() function:
    }
  12. Save the file. Then, in the new folder, run the following:
    go run .

The following is the output:

Figure 1.18: Output showing initial variable values

Figure 1.18: Output showing initial variable values

In this exercise, we defined a variety of variable types without an initial value. Then, we printed them out using fmt.Printf to expose more detail about the values. Knowing what the zero values are and how Go controls them allows you to avoid bugs and write concise code.

Next, we’ll look at what pointers are and how they can enable you to write efficient software.

Value versus pointer

With values such as int, bool, and string, when you pass them to a function, Go makes a copy of the value, and it’s the copy that’s used in the function. This copying means that a change that’s made to the value in the function doesn’t affect the value that you used when calling the function.

Passing values by copying tends to result in code that has fewer bugs. With this method of passing values, Go can use its simple memory management system, called the stack. The downside is that copying uses up more and more memory as values get passed from function to function. In real-world code, functions tend to be small, and values get passed to lots of functions, so copying by value can sometimes end up using much more memory than is needed.

There is an alternative to copying that uses less memory. Instead of passing a value, we create something called a pointer and then pass that to functions. A pointer is not a value itself, and you can’t do anything useful with a pointer other than getting a value using it. You can think of a pointer as the address of the value you want, and to get to the value, you must go to the address. If you use a pointer, Go won’t make a copy of the value when passing a pointer to a function.

When creating a pointer to a value, Go can’t manage the value’s memory using the stack. This is because the stack relies on simple scope logic to know when it can reclaim the memory that’s used by a value, and having a pointer to a variable means these rules don’t work. Instead, Go puts the value on the heap. The heap allows the value to exist until no part of your software has a pointer to it anymore. Go reclaims these values in what it calls its garbage collection process. This process happens periodically in the background, and you don’t need to worry about it.

Having a pointer to a value means that a value is put on the heap, but that’s not the only reason that happens. Working out whether a value needs to be put on the heap is called escape analysis. There are times when a value with no pointers is put on the heap, and it’s not always clear why.

You have no direct control over whether a value is put on the stack or the heap. Memory management is not part of Go’s language specification. Memory management is considered an internal implementation detail. This means it could be changed at any time, and that what we’ve spoken about are only general guidelines and not fixed rules and could change at a later date.

While the benefits of using a pointer over a value that gets passed to lots of functions are clear for memory usage, it’s not so clear for CPU usage. When a value gets copied, Go needs CPU cycles to get that memory and then release it later. Using a pointer avoids this CPU usage when passing it to a function. On the other hand, having a value on the heap means that it then needs to be managed by the complex garbage collection process. This process can become a CPU bottleneck in certain situations – for example, if there are lots of values on the heap. When this happens, the garbage collector has to do lots of checking, which uses up CPU cycles. There is no correct answer here, and the best approach is the classic performance optimization one. First, don’t prematurely optimize. When you do have a performance problem, measure before you make a change, and then measure after you’ve made a change.

Beyond performance, you can use pointers to change your code’s design. Sometimes, using pointers allows for a cleaner interface and simplifies your code. For example, if you need to know whether a value is present or not, a non-pointer value always has at least its zero value, which could be valid in your logic. You can use a pointer to allow for an is not set state as well as holding a value. This is because pointers, as well as holding the address to a value, can also be nil, which means there is no value. In Go, nil is a special type that represents something not having a value.

The ability for a pointer to be nil also means that it’s possible to get the value of a pointer when it doesn’t have a value associated with it, which means you’ll get a runtime error. To prevent runtime errors, you can compare a pointer to nil before trying to get its value. This looks like <pointer> != nil. You can compare pointers with other pointers of the same type, but they only result in true if you are comparing a pointer to itself. No comparison of the associated values gets made.

Pointers are powerful tools in the language thanks to their efficiency, ability to pass by reference (instead of pass by value) to allow functions to modify the original values, and how they allow for dynamic memory allocation using the garbage collector. However, with any great tool comes great responsibility. Pointers can be dangerous if misused, such as in the event memory is freed (deallocated) and the pointer becomes a “dangling pointer,” which could lead to undefined behavior if accessed. There is also the potential for memory leaks, unsafe operations due to direct memory access, and concurrency challenges if there are shared pointers that could introduce data races. Overall, Go’s pointers are generally straightforward and less error-prone compared to other languages such as C.

Getting a pointer

To get a pointer, you have a few options. You can declare a variable as being a pointer type using a var statement. You can do this by adding * at the front of most types. This notation looks like var <name> *<type>. The initial value of a variable that uses this method is nil. You can use the built-in new function for this. This function is intended to be used to get some memory for a type and return a pointer to that address. The notation looks like <name> := new(<type>). The new function can be used with var too. You can also get a pointer from an existing variable using &, which you can read as "address of". This looks like <var1> := &<var2>.

Exercise 1.13 – getting a pointer

In this exercise, we’ll use each of the methods we can use to get a pointer variable. Then, we’ll print them to the console using fmt.Printf to see what their types and value are. Let’s get started:

  1. Create a new folder and add a main.go file to it.
  2. In main.go, add the main package name to the top of the file:
    package main
  3. Import the packages we’ll need:
    import (
      "fmt"
      "time"
    )
  4. Create the main() function:
    func main() {
  5. Declare a pointer using a var statement:
      var count1 *int
  6. Create a variable using new:
      count2 := new(int)
  7. You can’t take the address of a literal number. Create a temporary variable to hold a number:
      countTemp := 5
  8. Using &, create a pointer from the existing variable:
      count3 := &countTemp
  9. It’s possible to create a pointer from some types without a temporary variable. Here, we’re using our trusty time struct:
      t := &time.Time{}
  10. Print each out using fmt.Printf:
      fmt.Printf("count1: %#v\n", count1)
      fmt.Printf("count2: %#v\n", count2)
      fmt.Printf("count3: %#v\n", count3)
      fmt.Printf("time : %#v\n", t)
  11. Close the main() function:
    }
  12. Save the file. Then, in the new folder, run the following:
    go run .

The following is the output:

Figure 1.19: Output showing pointers

Figure 1.19: Output showing pointers

In this exercise, we looked at three different ways of creating a pointer. Each one is useful, depending on what your code needs. With the var statement, the pointer has a value of nil, while the others already have a value address associated with them. For the time variable, we can see the value, but we can tell it’s a pointer because its output starts with &.

Next, we’ll see how we can get a value from a pointer.

Getting a value from a pointer

In the previous exercise, when we printed out the pointer variables for the int pointers to the console, we either got nil or saw a memory address. To get to the value a pointer is associated with, you must dereference the value using * in front of the variable name. This looks like fmt.Println(*<val>).

Dereferencing a zero or nil pointer is a common bug in Go software as the compiler can’t warn you about it, and it happens when the app is running. Therefore, it’s always best practice to check that a pointer is not nil before dereferencing it unless you are certain it’s not nil.

You don’t always need to dereference – for example, when a property or function is on a struct. Don’t worry too much about when you shouldn’t be dereferencing as Go gives you clear errors regarding when you can and can’t dereference a value.

Exercise 1.14 – getting a value from a pointer

In this exercise, we’ll update our previous exercise to dereference the values from the pointers. We’ll also add nil checks to prevent us from getting any errors. Let’s get started:

  1. Create a new folder and add a main.go file to it.
  2. In main.go, add the main package name to the top of the file:
    package main
  3. Import the packages we’ll need:
    import (
      "fmt"
      "time"
    )
  4. Create the main() function:
    func main() {
  5. Our pointers are declared in the same way as they were previously:
      var count1 *int
      count2 := new(int)
      countTemp := 5
      count3 := &countTemp
      t := &time.Time{}
  6. For counts 1, 2, and 3, we need to add a nil check and add * in front of the variable name:
      if count1 != nil {
        fmt.Printf("count1: %#v\n", *count1)
      }
      if count2 != nil {
        fmt.Printf("count2: %#v\n", *count2)
      }
      if count3 != nil {
        fmt.Printf("count3: %#v\n", *count3)
      }
  7. We’ll also add a nil check for our time variable:
      if t != nil {
  8. We’ll dereference the variable using *, just like we did with the count variables:
        fmt.Printf("time : %#v\n", *t)
  9. Here, we’re calling a function on our time variable. This time, we don’t need to dereference it:
        fmt.Printf("time : %#v\n", t.String())
  10. Close the nil check:
      }
  11. Close the main() function:
    }
  12. Save the file. Then, in the new folder, run the following:
    go run .

The following is the output:

Figure 1.20: Output showing getting values from pointers

Figure 1.20: Output showing getting values from pointers

In this exercise, we used dereferencing to get the values from our pointers. We also used nil checks to prevent dereferencing errors. From the output of this exercise, we can see that count1 was a nil value and that we’d have gotten an error if we tried to dereference. count2 was created using new, and its value is a zero value for its type. count3 also had a value that matched the value of the variable we got the pointer from. With our time variable, we were able to dereference the whole struct, which is why our output doesn’t start with &.

Next, we’ll look at how using a pointer allows us to change the design of our code.

Function design with pointers

We’ll cover functions in more detail later in this book, but you know enough from what we’ve done so far to see how using a pointer can change how you use a function. A function must be coded to accept pointers, and it’s not something that you can choose whether to do or not. If you have a pointer variable or have passed a pointer of a variable to a function, any changes that are made to the value of the variable in the function also affect the value of the variable outside of the function.

Exercise 1.15 – function design with pointers

In this exercise, we’ll create two functions: one that accepts a number by value, adds 5 to it, and then prints the number to the console; and another function that accepts a number as a pointer, adds 5 to it, and then prints the number out. We’ll also print the number out after calling each function to assess what effect it has on the variable that was passed to the function. Let’s get started:

  1. Create a new folder and add a main.go file to it.
  2. In main.go, add the main package name to the top of the file:
    package main
  3. Import the packages we’ll need:
    import "fmt"
  4. Create a function that takes an int pointer as an argument:
    func add5Value(count int) {
  5. Add 5 to the passed number:
      count += 5
  6. Print the updated number to the console:
      fmt.Println("add5Value   :", count)
  7. Close the function:
    }
  8. Create another function that takes an int pointer:
    func add5Point(count *int) {
  9. Dereference the value and add 5 to it:
      *count += 5
  10. Print out the updated value of count and dereference it:
      fmt.Println("add5Point   :", *count)
  11. Close the function:
    }
  12. Create the main() function:
    func main() {
  13. Declare an int variable:
      var count int
  14. Call the first function with the variable:
      add5Value(count)
  15. Print the current value of the variable:
      fmt.Println("add5Value post:", count)
  16. Call the second function. This time, you’ll need to use & to pass a pointer to the variable:
      add5Point(&count)
  17. Print the current value of the variable:
      fmt.Println("add5Point post:", count)
  18. Close the main() function:
    }
  19. Save the file. Then, in the new folder, run the following:
    go run .

The following is the output:

Figure 1.21: Output displaying the current value of the variable

Figure 1.21: Output displaying the current value of the variable

In this exercise, we showed you how passing values by a pointer can affect the value variables that are passed to them. We saw that, when passing by value, the changes you make to the value in a function do not affect the value of the variable that’s passed to the function, while passing a pointer to a value does change the value of the variable passed to the function.

You can use this fact to overcome awkward design problems and sometimes simplify the design of your code. Passing values by a pointer has traditionally been shown to be more error-prone, so use this design sparingly. It’s also common to use pointers in functions to create more efficient code, which Go’s standard library does a lot.

Activity 1.02 – pointer value swap

In this activity, your job is to finish some code a co-worker started. Here, we have some unfinished code for you to complete. Your task is to fill in the missing code, where the comments are to swap the values of a and b. The swap function only accepts pointers and doesn’t return anything:

package main
import "fmt"
func main() {
  a, b := 5, 10
  // call swap here
  fmt.Println(a == 10, b == 5)
}
func swap(a *int, b *int) {
  // swap the values here
}

Follow these steps:

  1. Call the swap function, ensuring you are passing a pointer.
  2. In the swap function, assign the values to the other pointer, ensuring you dereference the values.

The following is the expected output:

true true

Next, we’ll look at how we can create variables with a fixed value.

Constants

Constants are like variables, but you can’t change their initial values. These are useful for situations where the value of a constant doesn’t need to or shouldn’t change when your code is running. You could make the argument that you could hardcode those values into the code and it would have a similar effect. Experience has shown us that while these values don’t need to change at runtime, they may need to change later. If that happens, it can be an arduous and error-prone task to track down and fix all the hardcoded values. Using a constant is a tiny amount of work now that can save you a great deal of effort later.

Constant declarations are similar to var statements. With a constant, the initial value is required. Types are optional and inferred if left out. The initial value can be a literal or a simple statement and can use the values of other constants. Like var, you can declare multiple constants in one statement. Here are the notations:

constant <name> <type> = <value>
constant (
  <name1> <type1> = <value1>
  <name2> <type2> = <value3>
…
  <nameN> <typeN> = <valueN>
)

Exercise 1.16 – constants

In this exercise, we have a performance problem: our database server is too slow. We are going to create a custom memory cache. We’ll use Go’s map collection type, which will act as the cache. There is a global limit on the number of items that can be in the cache. We’ll use one map to help keep track of the number of items in the cache. We have two types of data we need to cache: books and CDs. Both use the ID, so we need a way to separate the two types of items in the shared cache. We need a way to set and get items from the cache.

We’re going to set the maximum number of items in the cache. We’ll also use constants to add a prefix to differentiate between books and CDs. Let’s get started:

  1. Create a new folder and add a main.go file to it.
  2. In main.go, add the main package name to the top of the file:
    package main
  3. Import the packages we’ll need:
    import "fmt"
  4. Create a constant that’s our global limit size:
    const GlobalLimit = 100
  5. Create a MaxCacheSize constant that is 10 times the global limit size:
    const MaxCacheSize int = 10 * GlobalLimit
  6. Create our cache prefixes:
    const (
      CacheKeyBook = "book_"
      CacheKeyCD = "cd_"
    )
  7. Declare a map value that has a string value for a key and a string value for its values as our cache:
    var cache map[string]string
  8. Create a function to get items from the cache:
    func cacheGet(key string) string {
      return cache[key]
    }
  9. Create a function that sets items in the cache:
    func cacheSet(key, val string) {
  10. In this function, check out the MaxCacheSize constant to stop the cache going over that size:
      if len(cache)+1 >= MaxCacheSize {
        return
      }
      cache[key] = val
    }
  11. Create a function to get a book from the cache:
    func GetBook(isbn string) string {
  12. Use the book cache prefix to create a unique key:
      return cacheGet(CacheKeyBook + isbn)
    }
  13. Create a function to add a book to the cache:
    func SetBook(isbn string, name string) {
  14. Use the book cache prefix to create a unique key:
      cacheSet(CacheKeyBook+isbn, name)
    }
  15. Create a function to get CD data from the cache:
    func GetCD(sku string) string {
  16. Use the CD cache prefix to create a unique key:
      return cacheGet(CacheKeyCD + sku)
    }
  17. Create a function to add CDs to the shared cache:
    func SetCD(sku string, title string) {
  18. Use the CD cache prefix constant to build a unique key for the shared cache:
      cacheSet(CacheKeyCD+sku, title)
    }
  19. Create the main() function:
    func main() {
  20. Initialize our cache by creating a map value:
      cache = make(map[string]string)
  21. Add a book to the cache:
      SetBook("1234-5678", "Get Ready To Go")
  22. Add a CD cache prefix to the cache:
      SetCD("1234-5678", "Get Ready To Go Audio Book")
  23. Get and print that Book from the cache:
      fmt.Println("Book :", GetBook("1234-5678"))
  24. Get and print that CD from the cache:
      fmt.Println("CD :", GetCD("1234-5678"))
  25. Close the main() function:
    }
  26. Save the file. Then, in the new folder, run the following:
    go run .

The following is the output:

Figure 1.22: Output displaying the Book and CD caches

Figure 1.22: Output displaying the Book and CD caches

In this exercise, we used constants to define values that don’t need to change while the code is running. We declared then using a variety of notation options, some with the typeset and some without. We declared a single constant and multiple constants in a single statement.

Next, we’ll look at a variation of constants for values that are more closely related.

Enums

Enums are a way of defining a fixed list of values that are all related. Go doesn’t have a built-in type for enums, but it does provide tools such as iota to let you define your own using constants. We’ll explore this now.

For example, in the following code, we have the days of the week defined as constants. This code is a good candidate for Go’s iota feature:

…
const (
  Sunday  = 0
  Monday  = 1
  Tuesday = 2
  Wednesday = 3
  Thursday = 4
  Friday  = 5
  Saturday = 6
)
…

With iota, Go helps us manage lists just like this. Using iota, the following code is equal to the preceding code:

…
const (
  Sunday = iota
  Monday
  Tuesday
  Wednesday
  Thursday
  Friday
  Saturday
)
…

Now, we have iota assigning the numbers for us. Using iota makes enums easier to create and maintain, especially if you need to add a new value to the middle of the code later. Order matters when using iota as it is an identifier that tells the Go compiler to start the first value at 0 and increment by 1 for each subsequent value in the case of this example. With iota, you can skip values using _, start with a different offset, and even use more complicated calculations.

Next, we’ll take a detailed look at Go’s variable scoping rules and how they affect how you write code.

Scope

All the variables in Go live in a scope. The top-level scope is the package scope. A scope can have child scopes within it. There are a few ways a child scope gets defined; the easiest way to think about this is that when you see {, you are starting a new child scope, and that child scope ends when you get to a matching }. The parent-child relationship is defined when the code compiles, not when the code runs. When accessing a variable, Go looks at the scope the code was defined in. If it can’t find a variable with that name, it looks in the parent scope, then the grandparent scope, all the way until it gets to the package scope. It stops looking once it finds a variable with a matching name or raises an error if it can’t find a match.

To put it another way, when your code uses a variable, Go needs to work out where that variable was defined. It starts its search in the scope of the code using the variable it’s currently running in. If a variable definition using that name is in that scope, then it stops looking and uses the variable definition to complete its work. If it can’t find a variable definition, then it starts walking up the stack of scopes, stopping as soon as it finds a variable with that name. This searching is all done based on a variable name. If a variable with that name is found but is of the wrong type, Go raises an error.

In this example, we have four different scopes, but we define the level variable once. This means that no matter where you use level, the same variable is used:

package main
import "fmt"
var level = "pkg"
func main() {
  fmt.Println("Main start :", level)
  if true {
    fmt.Println("Block start :", level)
    funcA()
  }
}
func funcA() {
  fmt.Println("funcA start :", level)
}

The following is the output displaying variables when using level:

Main start : pkg
Block start : pkg
funcA start : pkg

In this example, we’ve shadowed the level variable. This new level variable is not related to the level variable in the package scope. When we print level in the block, the Go runtime stops looking for variables called level as soon as it finds the one defined in main. This logic results in a different value getting printed out once that new variable shadows the package variable. You can also see that it’s a different variable because it’s a different type, and a variable can’t have its type changed in Go:

package main
import "fmt"
var level = "pkg"
func main() {
  fmt.Println("Main start :", level)
  // Create a shadow variable
  level := 42
  if true {
    fmt.Println("Block start :", level)
    funcA()
  }
  fmt.Println("Main end :", level)
}
func funcA() {
  fmt.Println("funcA start :", level)
}

The following is the output:

Main start : pkg
Block start : 42
funcA start : pkg
Main end : 42

Go’s static scope resolution comes into play when we call funcA. That’s why, when funcA runs, it still sees the package scope’s level variable. The scope resolution doesn’t pay attention to where funcA gets called.

You can’t access variables defined in a child scope:

package main
import "fmt"
func main() {
  {
    level := "Nest 1"
    fmt.Println("Block end :", level)
  }
  // Error: undefined: level
  //fmt.Println("Main end  :", level)
}

The following is the output:

Figure 1.23: Output displaying an error

Figure 1.23: Output displaying an error

Activity 1.03 – message bug

The following code doesn’t work. The person who wrote it can’t fix it, and they’ve asked you to help them. Can you get it to work?

package main
import "fmt"
func main() {
  count := 5
  if count > 5 {
    message := "Greater than 5"
  } else {
    message := "Not greater than 5"
  }
  fmt.Println(message)
}

Follow these steps:

  1. Run the code and see what the output is.
  2. The problem is with message; make a change to the code.
  3. Rerun the code and see what difference it makes.
  4. Repeat this process until you see the expected output.

    The following is the expected output:

    Not greater than 5

In this activity, we saw that where you define your variables has a big impact on the code. Always think about the scope you need your variables to be in when defining them.

In the next activity, we are going to look at a similar problem that is a bit trickier.

Activity 1.04 – bad count bug

Your friend is back, and they have another bug in their code. This code should print true, but it’s printing false. Can you help them fix the bug?

package main
import "fmt"
func main() {
  count := 0
  if count < 5 {
    count := 10
    count++
  }
  fmt.Println(count == 11)
}

Follow these steps:

  1. Run the code and see what the output is.
  2. The problem is with count; make a change to the code.
  3. Rerun the code and see what difference it makes.
  4. Repeat this process until you see the expected output.

The following is the expected output:

True

Summary

In this chapter, we got into the nitty-gritty of variables, including how variables are declared, and all the different notations you can use to declare them. This variety of notation gives you a nice compact notation to use for 90% of your work, while still giving you the power to be very specific when you need to the other 10% of the time. We looked at how to change and update the value of variables after you’ve declared them. Again, Go gives you some great shorthand to help in the most common use cases to make your life easier. All your data ends up in some form of variable. Data is what makes code dynamic and responsive. Without data, your code could only ever do exactly one thing; data unleashes the true power of software.

Now that your application has data, it needs to make choices based on that data. That’s where variable comparison comes in. This helps us see whether something is true or false, bigger or smaller, and so on, and it also helps us make choices based on the results of those comparisons.

We explored how Go decided to implement its variable system by looking at zero values, pointers, and scope logic. At this point, we know that these are the details that can be the difference between delivering bug-free efficient software and not doing so.

We also took a look at how we can declare immutable variables by using constants and how iota can help manage lists or related constants to work, such as enums.

In the next chapter, we’ll start to put our variables to work by defining logic and looping over collections of variables.

Left arrow icon Right arrow icon
Download code icon Download Code

Key benefits

  • Leverage Go's standard library through practical examples and simplify development tasks using best practices
  • Master effective idiomatic Go syntax, including variables, functions, and loops, to handle data
  • Build fully functional web applications with capabilities such as database connectivity and RESTful API creation
  • Purchase of the print or Kindle book includes a free PDF eBook

Description

Go Programming – From Beginner to Professional is a comprehensive guide that takes your proficiency in the Go programming language from novice to expert. Starting with fundamental concepts, this book covers variables, command-line tools, and working with data before delving into advanced concepts, including error handling, interfaces, and generics, harnessing Go’s latest features through hands-on exercises. Along the way, you’ll learn to structure projects using Go modules, manage packages effectively, and master debugging techniques. As you progress, you’ll get to grips with practical application-centric aspects such as command-line programming, file manipulation, and working with SQL databases. Additionally, the book explores web server development, RESTful APIs, and utilizing the Go HTTP client to interact with web applications. Further enhancing your Go skills, you’ll learn concurrent programming, testing methodologies, Go tools, and how to deploy applications in the cloud. Throughout the book, you’ll uncover Go’s hidden gems and gain insights into time manipulation, best practices, and more. By the end of this book, you’ll have worked through practical exercises and activities that’ll equip you with the knowledge and skills needed to excel as a proficient Go developer, primed for success in real-world projects.

What you will learn

Understand the Go syntax and apply it proficiently to handle data and write functions Debug your Go code to troubleshoot development problems Safely handle errors and recover from panics Implement polymorphism using interfaces and gain insight into generics Work with files and connect to popular external databases Create an HTTP client and server and work with a RESTful web API Use concurrency to design efficient software Use Go tools to simplify development and improve your code

Product Details

Country selected

Publication date : Mar 29, 2024
Length 680 pages
Edition : 2nd Edition
Language : English
ISBN-13 : 9781803243054
Category :
Languages :

What do you get with eBook?

Product feature icon Instant access to your Digital eBook purchase
Product feature icon Download this book in EPUB and PDF formats
Product feature icon AI Assistant (beta) to help accelerate your learning
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want

Product Details


Publication date : Mar 29, 2024
Length 680 pages
Edition : 2nd Edition
Language : English
ISBN-13 : 9781803243054
Category :
Languages :

Table of Contents

30 Chapters
Preface Chevron down icon Chevron up icon
1. Part 1: Scripts Chevron down icon Chevron up icon
2. Chapter 1: Variables and Operators Chevron down icon Chevron up icon
3. Chapter 2: Command and Control Chevron down icon Chevron up icon
4. Chapter 3: Core Types Chevron down icon Chevron up icon
5. Chapter 4: Complex Types Chevron down icon Chevron up icon
6. Part 2: Components Chevron down icon Chevron up icon
7. Chapter 5: Functions – Reduce, Reuse, and Recycle Chevron down icon Chevron up icon
8. Chapter 6: Don’t Panic! Handle Your Errors Chevron down icon Chevron up icon
9. Chapter 7: Interfaces Chevron down icon Chevron up icon
10. Chapter 8: Generic Algorithm Superpowers Chevron down icon Chevron up icon
11. Part 3: Modules Chevron down icon Chevron up icon
12. Chapter 9: Using Go Modules to Define a Project Chevron down icon Chevron up icon
13. Chapter 10: Packages Keep Projects Manageable Chevron down icon Chevron up icon
14. Chapter 11: Bug-Busting Debugging Skills Chevron down icon Chevron up icon
15. Chapter 12: About Time Chevron down icon Chevron up icon
16. Part 4: Applications Chevron down icon Chevron up icon
17. Chapter 13: Programming from the Command Line Chevron down icon Chevron up icon
18. Chapter 14: File and Systems Chevron down icon Chevron up icon
19. Chapter 15: SQL and Databases Chevron down icon Chevron up icon
20. Part 5: Building For The Web Chevron down icon Chevron up icon
21. Chapter 16: Web Servers Chevron down icon Chevron up icon
22. Chapter 17: Using the Go HTTP Client Chevron down icon Chevron up icon
23. Part 6: Professional Chevron down icon Chevron up icon
24. Chapter 18: Concurrent Work Chevron down icon Chevron up icon
25. Chapter 19: Testing Chevron down icon Chevron up icon
26. Chapter 20: Using Go Tools Chevron down icon Chevron up icon
27. Chapter 21: Go in the Cloud Chevron down icon Chevron up icon
28. Index Chevron down icon Chevron up icon
29. Other Books You May Enjoy Chevron down icon Chevron up icon

Customer reviews

Top Reviews
Rating distribution
Empty star icon Empty star icon Empty star icon Empty star icon Empty star icon 0
(0 Ratings)
5 star 0%
4 star 0%
3 star 0%
2 star 0%
1 star 0%
Top Reviews
No reviews found
Get free access to Packt library with over 7500+ books and video courses for 7 days!
Start Free Trial

FAQs

How do I buy and download an eBook? Chevron down icon Chevron up icon

Where there is an eBook version of a title available, you can buy it from the book details for that title. Add either the standalone eBook or the eBook and print book bundle to your shopping cart. Your eBook will show in your cart as a product on its own. After completing checkout and payment in the normal way, you will receive your receipt on the screen containing a link to a personalised PDF download file. This link will remain active for 30 days. You can download backup copies of the file by logging in to your account at any time.

If you already have Adobe reader installed, then clicking on the link will download and open the PDF file directly. If you don't, then save the PDF file on your machine and download the Reader to view it.

Please Note: Packt eBooks are non-returnable and non-refundable.

Packt eBook and Licensing When you buy an eBook from Packt Publishing, completing your purchase means you accept the terms of our licence agreement. Please read the full text of the agreement. In it we have tried to balance the need for the ebook to be usable for you the reader with our needs to protect the rights of us as Publishers and of our authors. In summary, the agreement says:

  • You may make copies of your eBook for your own use onto any machine
  • You may not pass copies of the eBook on to anyone else
How can I make a purchase on your website? Chevron down icon Chevron up icon

If you want to purchase a video course, eBook or Bundle (Print+eBook) please follow below steps:

  1. Register on our website using your email address and the password.
  2. Search for the title by name or ISBN using the search option.
  3. Select the title you want to purchase.
  4. Choose the format you wish to purchase the title in; if you order the Print Book, you get a free eBook copy of the same title. 
  5. Proceed with the checkout process (payment to be made using Credit Card, Debit Cart, or PayPal)
Where can I access support around an eBook? Chevron down icon Chevron up icon
  • If you experience a problem with using or installing Adobe Reader, the contact Adobe directly.
  • To view the errata for the book, see www.packtpub.com/support and view the pages for the title you have.
  • To view your account details or to download a new copy of the book go to www.packtpub.com/account
  • To contact us directly if a problem is not resolved, use www.packtpub.com/contact-us
What eBook formats do Packt support? Chevron down icon Chevron up icon

Our eBooks are currently available in a variety of formats such as PDF and ePubs. In the future, this may well change with trends and development in technology, but please note that our PDFs are not Adobe eBook Reader format, which has greater restrictions on security.

You will need to use Adobe Reader v9 or later in order to read Packt's PDF eBooks.

What are the benefits of eBooks? Chevron down icon Chevron up icon
  • You can get the information you need immediately
  • You can easily take them with you on a laptop
  • You can download them an unlimited number of times
  • You can print them out
  • They are copy-paste enabled
  • They are searchable
  • There is no password protection
  • They are lower price than print
  • They save resources and space
What is an eBook? Chevron down icon Chevron up icon

Packt eBooks are a complete electronic version of the print edition, available in PDF and ePub formats. Every piece of content down to the page numbering is the same. Because we save the costs of printing and shipping the book to you, we are able to offer eBooks at a lower cost than print editions.

When you have purchased an eBook, simply login to your account and click on the link in Your Download Area. We recommend you saving the file to your hard drive before opening it.

For optimal viewing of our eBooks, we recommend you download and install the free Adobe Reader version 9.