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

Getting to know about structs

Structs represent a collection of variables. In the real world, we work with data all the time that would be well represented by a struct. For example, any form that is filled out in a job application or a vaccine card is a collection of variables (for example, last name, first name, and government ID number) that each has types (for example, string, int, and float64) and are grouped together. That grouping would be a struct in Go.

Declaring a struct

There are two methods for declaring a struct. The first way is uncommon except in tests, as it doesn't allow us to reuse the struct's definition to create more variables. But, as we will see it later in tests, we will cover it here:

var record = struct{
     Name string
     Age int
}{
     Name: "John Doak",
     Age: 100, // Yeah, not publishing the real one
}

Here, we created a struct that contains two fields:

  • Name (string)
  • Age (int)

We then created an instance of that struct that has those values set. To access those fields, we can use the dot . operator:

fmt.Printf("%s is %d years old\n", record.Name, record.Age)

This prints "John Doak is 100 years old".

Declaring single-use structs, as we have here, is rarely done. Structs become more useful when they are used to create custom types in Go that are reusable. Let's have a look at how we can do that next.

Declaring a custom type

So far, we have created a single-use struct, which generally is not useful. Before we talk about the more common way to do this, let's talk about creating custom types.

Up until this point, we've seen the basic and pointer-wrapped types that are defined by the language: string, bool, map, and slice, for example. We can create our own types based on these basic types using the type keyword. Let's create a new type called CarModel that is based on the string type:

type CarModel string

CarModel is now its own type, just like string. While CarModel is based on a string type, it is a distinct type. You cannot use CarModel in place of a string or vice versa.

Creating a variable of CarModel can be done similar to a string type:

var myCar CarModel = "Chevelle"

Or, by using type conversion, as shown here:

myCar = CarModel("Chevelle") 

Because CarModel is based on string, we can convert CarModel back to string with type conversion:

myCarAsString := string(myCar)

We can create new types based on any other type, including maps, slices, and functions. This can be useful for naming purposes or adding custom methods to a type (we will talk about this in a moment).

Custom struct types

The most common way to declare a struct is using the type keyword. Let's create that record again, but this time let's make it reusable by declaring a type:

type Record struct{
     Name string
     Age int
}
func main() {
     david := Record{Name: "David Justice", Age: 28}
     sarah := Record{Name: "Sarah Murphy", Age: 28}
     fmt.Printf("%+v\n", david)
     fmt.Printf("%+v\n", sarah)
}

By using type, we have made a new type called Record that we can use again and again to create variables holding Name and Age.

Note

Similar to how you may define two variables with the same type on a single line, you may do the same within a struct type, such as First, Last string.

Adding methods to a type

A method is similar to a function, but instead of being independent, it is bound to a type. For example, we have been using the fmt.Println() function. That function is independent of any variable that has been declared.

A method is a function that is attached to a variable. It can only be used on a variable of a type. Let's create a method that returns a string representation of the Record type we created earlier:

type Record struct{
     Name string
     Age int
}
// String returns a csv representing our record.
func (r Record) String() string {
     return fmt.Sprintf("%s,%d", r.Name, r.Age)
}

Notice func (r Record), which attaches the function as a method onto the Record struct. You can access the fields of Record within this method by using r.<field>, such as r.Name or r.Age.

This method cannot be used outside of a Record object. Here's an example of using it:

john := Record{Name: "John Doak", Age: 100}
fmt.Println(john.String())

Let's look at how we change a field's value.

Changing a field's value

Struct values can be changed by using the variable attribute followed by = and the new value. Here is an example:

myRecord.Name = "Peter Griffin"
fmt.Println(myRecord.Name) // Prints: Peter Griffin

It is important to remember that a struct is not a reference type. If you pass a variable representing a struct to a function and change a field in the function, it will not change on the outside. Here is an example:

func changeName(r Record) {
     r.Name = "Peter"
     fmt.Println("inside changeName: ", r.Name)
}
func main() {
     rec := Record{Name: "John"}
     changeName(rec)
     fmt.Println("main: ", rec.Name)
}

This will output the following:

Inside changeName: Peter 
Main: John

As we learned in the section on pointers, this is because the variable is copied, and we are changing the copy. For struct types that need to have fields that change, we normally pass in a pointer. Let's try this again, using pointers:

func changeName(r *Record) {
	r.Name = "Peter"
	fmt.Println("inside changeName: ", r.Name)
}
func main() {
	// Create a pointer to a Record
	rec := &Record{Name: "John"}
	changeName(rec)
	fmt.Println("main: ", rec.Name)
}
Inside changeName: Peter
Main: Peter

This will output the following:

Inside changeName: Peter 
Main: Peter

Note that . is a magic operator that works on struct or *struct.

When I declared the rec variable, I did not set the age. Non-set fields are set to the zero value of the type. In the case of Age, which is int, this would be 0.

Changing a field's value in a method

In the same way that a function cannot alter a non-pointer struct, neither can a method. If we had a method called IncrAge() that increased the age on the record by one, this would not do what you wanted:

func (r Record) IncrAge() {
     r.Age++
}

The preceding code passes a copy of Record, adds one to the copy's Age, and returns.

To actually increment the age, simple make Record a pointer, as follows:

func (r *Record) IncrAge() {
     r.Age++
}

This will work as expected.

Tip

Here is a basic rule that will keep you out of trouble, especially when you are new to the language. If the struct type should be a pointer, then make all methods pointer methods. If it shouldn't be, then make them all non-pointers. Don't mix and match.

Constructors

In many languages, constructors are specially-declared methods or syntax that are used to initialize fields in an object and sometimes run internal methods as setup. Go doesn't provide any specialized code for that, instead, we use a constructor pattern using simple functions.

Constructors are commonly either called New() or New[Type]() when declaring a public constructor. Use New() if there are no other types in the package (and most likely won't be in the future).

If we wanted to create a constructor that made our Record from the previous section, it might look like the following:

func NewRecord(name string, age int) (*Record, error) {
     if name == "" {
          return nil, fmt.Errorf("name cannot be the empty string")
     }
     if age <= 0 {
          return nil, fmt.Errorf("age cannot be <= 0")
     }
     return &Record{Name: name, Age: age}, nil
}

This constructor takes in a name and age argument and returns a pointer to Record with those fields set. If we pass bad values for those fields, it instead returns the pointer's zero value (nil) and an error. Using this looks like the following:

     rec, err := NewRecord("John Doak", 100)
     if err != nil {
          return err
     }

Don't worry about the error, as we will discuss it in the course of the book's journey.

By now, you have learned how to use struct, Go's base object type. This included creating a struct, creating custom structs, adding methods, changing field values, and creating constructor functions. Now, let's look at using Go interfaces to abstract types.

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