Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
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

Comprehending Go interfaces

Go provides a type called an interface that stores any value that declares a set of methods. The implementing value must have declared this set of methods to implement the interface. The value may also have other methods besides the set declared in the interface type.

If you are new to interfaces, understand that they can be a little confusing. Therefore, we will take it one step at a time.

Defining an interface type

Interfaces are most commonly defined using the type keyword that we discussed in the earlier section on structs. The following defines an interface that returns a string representing the data:

type Stringer interface {
          String() string
}

Note

Stringer is a real type defined in the standard library's fmt package. Types that implement Stringer will have their String() method called when passed to print functions in the fmt package. Don't let the similar names confuse you; Stringer is the interface type's name, and it defines a method called String() (which is uppercase to distinguish it from the string type, which is lowercase). That method returns a string type that should provide some human-readable representation of your data.

Now, we have a new type called Stringer. Any variable that has the String() string method can be stored in a variable of type Stringer. The following is an example:

type Person struct {
     First, Last string
}
func (p Person) String() string {
     return fmt.Sprintf("%s,%s", p.Last, p.First)
}

Person represents a record of a person, first and last name. We define String() string on it, so Person implements Stringer:

type StrList []string
func (s StrList) String() string {
     return strings.Join(s, ",")
}

StrList is a slice of strings. It also implements Stringer. The strings.Join() function used here takes a slice of strings and creates a single string with each entry from the slice separated by a comma:

// PrintStringer prints the value of a Stringer to stdout.
func PrintStringer(s Stringer) {
     fmt.Println(s.String())
}

PrintStringer() allows us to print the output of Stringer.String() of any type that implements Stringer. Both the types we created above implement Stringer.

Let's see this in action:

func main() { 
    john := Person{First: "John", Last: "Doak"} 
    var nameList Stringer = StrList{"David", "Sarah"} 
    PrintStringer(john)     // Prints: Doak,John 
    PrintStringer(nameList) // Prints: David,Sarah 
} 

Without interfaces, we would have to write a separate Print[Type] function for every type we wanted to print. Interfaces allow us to pass values that can do common operations defined by their methods.

Important things about interfaces

The first thing to note about interfaces is that values must implement every method defined in the interface. Your value can have methods not defined for the interface, but it doesn't work the other way.

Another common issue new Go developers encounter is that once the type is stored in an interface, you cannot access its fields, or any methods not defined on the interface.

The blank interface – Go's universal value

Let's define a blank interface variable: var i interface{}. i is an interface with no defined methods. So, what can you store in that?

That's right, you can store anything.

interface{} is Go's universal value container that can be used to pass any value to a function and then figure out what it is and what to do with it later. Let's put some things in i:

i = 3
i = "hello world"
i = 3.4
i = Person{First: "John"}

This is all legal because each of those values has types that define all the methods that the interface defined (which were no methods). This allows us to pass around values in a universal container. This is actually how fmt.Printf() and fmt.Println() work. Here are their definitions from the fmt package:

func Println(a ...interface{}) (n int, err error)
func Printf(format string, a ...interface{}) (n int, err error)

However, as the interface did not define any methods, i is not useful in this form. So, this is great for passing around values, but not using them.

Note about interface{} in 1.18:

Go 1.18 has introduced an alias for the blank interface{}, called any. The Go standard library now uses any in place of interface{}. However, all packages prior to 1.18 will still use interface{}. Both are equivalent and can be used interchangeably.

Type assertion

Interfaces can have their values asserted to either another interface type or to their original type. This is different than type conversion, where you change the type from one to another. In this case, we are saying it already is this type.

Type assertion allows us to change an interface{} value into a value that we can do something with.

There are two common ways to do this. The first uses the if syntax, as follows:

if v, ok := i.(string); ok {
     fmt.Println(v)
}

i.(string) is asserting that i is a string value. If it is not, ok == false. If ok == true, then v will be the string value.

The more common way is with a switch statement and another use of the type keyword:

switch v := i.(type) {
case int:
     fmt.Printf("i was %d\n", i)
case string:
     fmt.Printf("i was %s\n", i)
case float:
     fmt.Printf("i was %v\n", i)
case Person, *Person:
     fmt.Printf("i was %v\n", i)
default:
     // %T will print i's underlying type out
     fmt.Printf("i was an unsupported type %T\n", i)
}

Our default statement prints out the underlying type of i if it did not match any of the other cases. %T is used to print the type information.

In this section, we learned about Go's interface type, how it can be used to provide type abstraction, and converting an interface into its concrete type for use.

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
Banner background image