Search icon CANCEL
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Go for DevOps

You're reading from   Go for DevOps Learn how to use the Go language to automate servers, the cloud, Kubernetes, GitHub, Packer, and Terraform

Arrow left icon
Product type Paperback
Published in Jul 2022
Publisher Packt
ISBN-13 9781801818896
Length 634 pages
Edition 1st Edition
Languages
Tools
Concepts
Arrow right icon
Authors (2):
Arrow left icon
John Doak John Doak
Author Profile Icon John Doak
John Doak
David Justice David Justice
Author Profile Icon David Justice
David Justice
Arrow right icon
View More author details
Toc

Table of Contents (22) Chapters Close

Preface 1. Section 1: Getting Up and Running with Go
2. Chapter 1: Go Language Basics FREE CHAPTER 3. Chapter 2: Go Language Essentials 4. Chapter 3: Setting Up Your Environment 5. Chapter 4: Filesystem Interactions 6. Chapter 5: Using Common Data Formats 7. Chapter 6: Interacting with Remote Data Sources 8. Chapter 7: Writing Command-Line Tooling 9. Chapter 8: Automating Command-Line Tasks 10. Section 2: Instrumenting, Observing, and Responding
11. Chapter 9: Observability with OpenTelemetry 12. Chapter 10: Automating Workflows with GitHub Actions 13. Chapter 11: Using ChatOps to Increase Efficiency 14. Section 3: Cloud ready Go
15. Chapter 12: Creating Immutable Infrastructure Using Packer 16. Chapter 13: Infrastructure as Code with Terraform 17. Chapter 14: Deploying and Building Applications in Kubernetes 18. Chapter 15: Programming the Cloud 19. Chapter 16: Designing for Chaos 20. Index 21. Other Books You May Enjoy

Understanding Go pointers

Pointers are another essential tool for programming languages for efficient memory use. Some readers may have not encountered pointers in their current language, instead having used its cousin, the reference type. In Python, for example, the dict, list, and object types are reference types.

In this section, we will cover what pointers are, how to declare them, and how to use them.

Memory addresses

In an earlier chapter, we talked about variables for storing data of some type. For example, if we want to create a variable called x that stores an int type with a value of 23, we can write var x int = 23.

Under the hood, the memory allocator allocates us space to store the value. The space is referenced by a unique memory address that looks like 0xc000122020. This is similar to how a home address is used; it is the reference to where the data lives.

We can see the memory address where a variable is stored by prepending & to a variable name:

fmt.Println(&x)

This would print 0xc000122020, the memory address of where x is stored.

This leads to an important concept: functions always make a copy of the arguments passed.

Function arguments are copies

When we call a function and pass a variable as a function argument, inside the function you get a copy of that variable. This is important because when you change the variable, you are only affecting the copy inside the function.

func changeValue(word string) {
     word += "world" 
}

In this code, word is a copy of the value that was passed. word will stop existing at the end of this function call.

func main() {
     say := "hello"
     changeValue(say)
     fmt.Println(say)
}

This prints "hello". Passing the string and changing it in the function doesn't work, because inside the function we are working with a copy. Think of every function call as making a copy of the variable with a copy machine. Editing the copy that came out of the copy machine does not affect the original.

Pointers to the rescue

Pointers in Go are types that store the address of a value, not the value. So, instead of storing 23, it would store 0xc000122020, which is where in memory 23 is stored.

A pointer type can be declared by prepending the type name with *. If we want to create an intPtr variable that stores a pointer to int, we can do the following:

var intPtr *int

You cannot store int in intPtr; you can only store the address of int. To get the address of an existing int, you can use the & symbol on a variable representing int.

Let's assign intPtr the address of our x variable from previously:

intPtr = &x
intPtr now stores 0xc000122020. 

Now for the big question, how is this useful? This lets us refer to a value in memory and change that value. We do that through what is called dereferencing the pointer. This is done with the * operator on the variable.

We can view or change the value held at x by dereferencing the pointer. The following is an example:

fmt.Println(x)             // Will print 23 
fmt.Println(*intPtr)       // Will print 23, the value at x 
*intPtr = 80               // Changes the value at x to 80 
fmt.Println(x)             // Will print 80 

This also works across functions. Let's alter changeValue() to work with pointers:

func changeValue(word *string) {
     // Add "world" to the string pointed to by 'word'
     *word += "world"
}
func main() {
     say := "hello"
     changeValue(&say) // Pass a pointer
     fmt.Println(say) // Prints "helloworld"
}

Note that operators such as * are called overloaded operators. Their meaning depends on the context in which they are used. When declaring a variable, * indicates a pointer type, var intPtr *int. When used on a variable, * means dereference, fmt.Println(*intPtr). When used between two numbers, it means multiply, y := 10 * 2. It takes time to remember what a symbol means when used in certain contexts.

But, didn't you say every argument is a copy?!

I did indeed. When you pass a pointer to a function, a copy of the pointer is made, but the copy still holds the same memory address. Therefore, it still refers to the same piece of memory. It is a lot like making a copy of a treasure map on the copy machine; the copy still points to the place in the world where you will find the treasure. Some of you are probably thinking, But maps and slices can have their values changed, what gives?

They are a special type called a pointer-wrapped type. A pointer-wrapped type hides internal pointers.

Don't go crazy with pointers

While in our examples we used pointers for basic types, typically pointers are used on long-lived objects or for storage of large data that is expensive to copy. Go's memory model uses the stack/heap model. Stack memory is created for exclusive use by a function/method call. Allocation on the stack is significantly faster than on the heap.

Heap allocation occurs in Go when a reference or pointer cannot be determined to live exclusively within a function's call stack. This is determined by the compiler doing escape analysis.

Generally, it is much cheaper to pass copies into a function via an argument and another copy in the return value than it is to use a pointer. Finally, be careful with the number of pointers. Unlike C, it is uncommon in Go to see pointers to pointers, such as **someType, and, in over 10 years of coding Go, I have only once seen a single use for ***someType that was valid. Unlike in the movie Inception, there is no reason to go deeper.

To sum up this section, you have gained an understanding of pointers, how to declare them, how to use them in your code, and where you should probably use them. You will use them on long-lived objects or types holding large amounts of data where copies are expensive. Next, let's explore structs.

You have been reading a chapter from
Go for DevOps
Published in: Jul 2022
Publisher: Packt
ISBN-13: 9781801818896
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime