Page
1
(This page has no text content)
Page
2
Know Go John Arundel Bitfield Consulting February 12, 2025 © 2022 John Arundel
Page
3
Know Go Praise for ‘Know Go’ Introduction About the book About generics Who is the book for? What should I read first? What will I learn from this book? Completing the challenges How do I install the Go tools? Where are the code examples? 1. Interfaces Programming with types Specific programming Generic programming Interface types Interface parameters Polymorphism Interfaces make code flexible Constraining parameters with interfaces Limitations of method sets The empty interface: any Type assertions and switches Go, meet generics How it started How it’s going 2. Type parameters Generic functions Introducing T, the arbitrary type Type parameters Instantiation Stencilling
Page
4
Getting started An Identity function Instantiating Identity Exercise: Hello, generics Running the test Composite types Slices of some arbitrary type Other generic composite types Generic types Defining a generic slice type The elements all have the same type Generic types need to be instantiated Exercise: Group therapy Generic function types Generic functions as values The type is always instantiated There are no generic functions Generic types as function parameters Exercise: Lengthy proceedings Constraining type parameters We can’t add any to any Not every type is “addable” 3. Constraints Method set constraints Limitations of the any constraint Basic interfaces Exercise: Stringy beans Type set constraints Type elements Using a type set constraint Unions The set of all allowed types Intersections Empty type sets Composite type literals A struct type literal
Page
5
Access to struct fields Some limitations of type sets Constraints versus basic interfaces Constraints are not classes Approximations Limitations of named types Type approximations Derived types Exercise: A first approximation Interface literals Syntax of an interface literal Omitting the interface keyword Referring to type parameters Exercise: Greater love 4. Operations Arithmetic An AddAnything function The set of all numeric types Exercise: Product placement Ordered types The > operator Strings An Ordered interface The cmp.Ordered constraint Multiple type parameters Function on two (or more) types Each type parameter is a distinct type Functions on slice types The problem with derived slice types Parameterizing by slice and element type And I need to know this because…? Comparable types Not every type is comparable Not every comparable type is ordered There are infinitely many comparable types The comparable constraint
Page
6
Why is comparable predeclared? Exercise: Duplicate keys Abstract types A Greatest function The scope of type parameters The zero value Switching on abstract types 5. Types Named types Named basic types Generic basic types Generic slice types There are no generic types Slices of interface types Generic map types Maps of abstract element types Multiple type parameters Instantiating multiple type parameters Generic struct types Self-reference Methods Adding methods to generic types Exercise: Empty promises Parameterised methods More generic composite types Interfaces Channels 6. Functions Functions on container types Contains Reverse Sort First-class functions Map Type inference Exercise: Func to funky
Page
7
Filtering and reduction Filter Filter functions Generic filter functions Reduce Implementing Reduce Other reduction operations Other considerations Constraints Concurrency Exercise: Compose yourself 7. Containers Sets Maps as sets Operations on sets Designing the Set type Building out the machinery The Add method The Contains method Initialising with multiple values Getting a set’s members A String method Logic on sets Union Intersection A real-life example Exercise: Stack overflow 8. Concurrency Data races Mutexes Deadlocks A concurrency-safe set type Locking The constructor A locking Add method The read-locking methods
Page
8
Testing concurrency safety Smoke tests A fatal error The race detector And the rest Exercise: Channelling frustration Extending the Set type More set operations Key-value stores and caches Other container types 9. Packages The cmp package The slices package Comparing slices Finding elements Maxima and minima Inserting and deleting Cloning and compacting Growing and shrinking Sorting Reversing and replacing Searching The maps package Comparing maps Deleting map entries Cloning and copying New idioms Copying slices Deleting slice elements Checking for slice elements Exercise: Merging in turn 10. Questions Change How much do I need to know about generics? Will generics drastically change the way I write Go? Do I need to change my existing code?
Page
9
Performance What impact does generics have on performance? Does generic code compile more slowly? Downsides Why didn’t they use angle brackets? Still no option types Still no enums No proper union types No macros No parameterised methods No generic packages No “insert cool tech here” More change Will there be a “Go 2”? There will be changes to Go It won’t be “Go 2” But there will be a successor to Go (someday) 11. Iterators Introduction to iterators Why are iterators useful? The signature of an iterator function Using iterators in programs Single-value iterators: iter.Seq Two-value iterators: iter.Seq2 Dealing with errors Cleaning up Embracing iterators Composing iterators When iterators beat channels Standard library changes Slices Maps About this book Who wrote this? Feedback Free updates to future editions
Page
10
Join my Go Club For the Love of Go The Power of Go: Tools Further reading Credits Acknowledgements
Page
11
Praise for ‘Know Go’ Everything I wanted to know about generics in Go, beautifully explained! —Pavel Anni I really loved reading this book. I found the idea of generics scary at first, but now I’m very comfortable with it thanks to John’s simple yet complete way of teaching. —Shivdas Patil It’s taken me from being apprehensive about learning something new to being excited about the possibilities that generics brings. —Dan Macklin Well written: the explanations and examples are clear and easy to understand. —Pedro Sandoval One day’s exposure to mountains is better than a cartload of books. —John Muir
Page
12
Introduction The more I use Go, the more I think generics would be a useless misfeature. Why do so many people think they need them? —Aram Hăvărneanu Hello, welcome to the book, and welcome to the world of generic programming in Go! I’m looking forward to exploring it with you. About the book This book is about generics in Go (among other things). We’ll talk about exactly what that means in more detail later on, but, briefly, it’s about defining (and using) generic functions and generic types. About generics First, what’s a generic function? It’s one that doesn’t specify the types of all its parameters in advance. Instead, some
Page
13
types are represented by placeholders, called type parameters. Generic types have the same property: some or all of the types involved aren’t specified exactly. For example, we might want to define a slice of elements of some unspecified type, or a struct with some field of a type that will be known later. Like many things in programming, generics sounds complicated at first, but once you get your head round it, it’s actually quite straightforward. In this book, we’ll work together through the steps necessary to understand what generic functions and types are, why they’re useful, how they work in Go, and what fun and interesting things we can do with them. Who is the book for? This book is for people who are new to the generics and iterators features in Go and want to know what they are, how to use them, and what they should do differently now that Go has them. If you have some experience using Go prior to the introduction of these features, and you just want to know what’s new, you’ll find everything you need to know right here. If you’re used to using generics and iterators in other languages, such as Java or C++, and you’d like to know how that experience will translate to Go, this book is also for you. If you’ve considered using Go in the past but decided against it for one reason or another, maybe the latest changes will tip the balance for you. This book will help you decide whether you’ll be able to do what you want to do with Go.
Page
14
And whether you have any experience with Go or not, you may be worried that these recent changes add unnecessary complexity to the language and will make it harder for you to understand, or even write, programs. This book is for you too! I hope you’ll find that generic programming in Go isn’t as difficult or complicated as it might sound. In fact, it’s extremely straightforward, when we approach it the right way. What should I read first? If you’re completely new to Go, or even to programming, I recommend you read my previous book, For the Love of Go, first. It’ll give you a good grounding in the basics of Go, which will help you understand the material in this book more easily: What will I learn from this book? By reading through this book and completing the exercises, you’ll learn: What we mean by generic programming in general, and specifically how that applies to Go What type parameters are, and how they differ from interfaces How to declare and write generic functions, and when that’s necessary (and when it’s not) How generic functions and types are implemented in Go, and how that affects the way we write programs How to define and use constraints on type parameters, and what constraints are provided in standard library
Page
15
packages and the Go language itself How to write type element constraints and type approximations What operations are allowed on parameterised types, and how to choose the right constraints for them How to define generic types, such as maps, slices, and structs, and how to write methods on such types. How to write generic functions, such as map, reduce, and filter operations on slices, and how to combine first- class functions with generics in a useful way. How generics support enables us to create some interesting data structures such as sets, trees, graphs, heaps, and queues. What iterators are, and how to create and use them, along with the new iterator APIs in the standard library. Completing the challenges You can learn a lot by just reading, of course, but you’ll learn much more by taking on the many interactive code challenges throughout the book, and solving them. How do I install the Go tools? If a suitable version of Go isn’t yet available as a package in your operating system distribution, you can build it from source or download a suitable binary package from the Go website directly: https://go.dev/learn/
Page
16
Where are the code examples? There are lots of puzzles for you to solve throughout the book, each designed to help you test your understanding of the concepts you’ve just learned. If you run into trouble, or just want to check your code, each exercise is accompanied by a complete sample solution, with tests. All these solutions are also available in a public GitHub repo here: https://github.com/bitfield/know-go Each exercise in the book is accompanied by a link to an example solution in the GitHub repo.
Page
17
1. Interfaces I went to a general store, but they wouldn’t let me buy anything specific. —Steven Wright Programming with types Still with me? Great. Let’s lay the groundwork a little by talking about types. Specific programming I’m sure you know that Go has data types: numbers, strings, and so on. Every variable and value in Go has some type, whether it’s a built-in type such as int, or a user-defined type such as a struct. Go keeps track of these types, and you’ll be well aware that it won’t let you get away with any type mismatches, such as trying to assign an int value to a float64 variable.
Page
18
And you’ve probably written functions in Go that take some specific type of parameter, such as a string. If we tried to pass a value of a different type, Go would complain. So that’s specific programming, if you like: writing functions that take parameters of some specific type. And that’s the kind of programming you’re probably used to doing in Go. Generic programming What would generic programming be, then? It would have to be writing functions that can take either any type of parameter, or, more usefully, a set of possible types. The generic equivalent of our PrintString function, for example, might be able to print not just a string, but any type of value. What would that look like? What kind of parameter type would we declare? It’s tricky, because we have to put something in the function’s parameter list, and we simply don’t know what to write there yet. We will learn how generics solves this problem in a moment, but first let’s look at some other ways we could have achieved the same goal. Interface types Go has always had a limited kind of support for functions that can take an argument of more than one specific type, using interfaces. You might have encountered interface types like io.Writer, for example. Here’s a function that declares a parameter w of type io.Writer:
Page
19
Here we don’t know what the precise type of the argument w will be at run time (its dynamic type, we say), but we (and Go) can at least say something about it. We can say that it must implement the interface io.Writer. What does it mean to implement an interface? Well, we can look at the interface definition for clues: What this is saying is that to be an io.Writer—to implement io.Writer—is to have a particular set of methods. In this case, just one method, Write, with a particular signature (it must take a []byte parameter and return int and error). This means that more than one type can implement io.Writer. In fact, any type that has a suitable Write method implements it automatically. You don’t even need to explicitly declare that your type implements a certain interface. If you have the right set of methods, you implicitly implement any interface that specifies those methods. For example, we can define some struct type of our own, and give it a Write method that does nothing at all: func PrintTo(w io.Writer, msg string) { fmt.Fprintln(w, msg) } type Writer interface { Write(p []byte) (n int, err error) }
Page
20
We now know that the presence of this Write method implicitly makes our struct type an io.Writer. So we could pass an instance of MyWriter to any function that expects an io.Writer parameter, for example. Interface parameters The MyWriter type may not be very useful in practice, since it doesn’t do anything. Nonetheless, any value of type MyWriter is a valid io.Writer, because it has the required Write method. It can have other methods, too, but Go doesn’t care about that when it’s deciding whether or not a MyWriter is an io.Writer. It just needs to see a Write method with the correct signature. This means that we can pass an instance of MyWriter to PrintTo, for example: If we tried to pass a value of some other type that doesn’t satisfy the interface, we feel like it shouldn’t work: type MyWriter struct {} func (MyWriter) Write([]byte) (int, error) { return 0, nil } PrintTo(MyWriter{}, "Hello, world!") type BogusWriter struct{} PrintTo(BogusWriter{}, "This won't compile!")