150 96 3MB
English Pages [156] Year 2024

Contents 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? .
8 . . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
9 9 9 10 10 10 11 11 11
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 . . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
12 12 12 13 13 14 15 15 15 16 17 17 18 18 19
2. Type parameters Generic functions . . . . . . . . . . Introducing T, the arbitrary type Type parameters . . . . . . . . Instantiation . . . . . . . . . . Stencilling . . . . . . . . . . . Getting started . . . . . . . . . . . . An Identity function . . . . . Instantiating Identity . . . . . Exercise: Hello, generics . . . . Running the test . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
20 20 20 21 21 22 22 23 23 24 24
. . . . . . . .
. . . . . . . . . .
2
. . . . . . . .
. . . . . . . . . .
. . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
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 . . . . . . . . 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 . . . . . . . 3
. . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
25 26 26 26 27 27 27 28 29 29 29 30 30 31 32 32 32
. . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . .
34 34 34 35 36 37 37 37 38 39 39 39 40 40 40 41 41 41 42 42 43 43 44 45 45 46 47 48
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 . . . . . . . . Why is comparable predeclared? . . . . . Exercise: Duplicate keys . . . . . . . . . . Abstract types . . . . . . . . . . . . . . . . . . A Greatest function . . . . . . . . . . . . The scope of type parameters . . . . . . . . The zero value . . . . . . . . . . . . . . . Switching on abstract types . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
50 50 51 51 52 54 54 55 55 56 56 57 57 57 58 59 60 60 60 61 61 62 62 62 64 64 65 65 66
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 . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
68 68 69 69 69 70 70 71 71 72 72 73 73 74 74 74
4
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
Parameterised methods . More generic composite types Interfaces . . . . . . . . Channels . . . . . . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
75 76 76 76
6. Functions Functions on container types . . Contains . . . . . . . . . . Reverse . . . . . . . . . . Sort . . . . . . . . . . . . First‐class functions . . . . . . . Map . . . . . . . . . . . . . Type inference . . . . . . . Exercise: Func to funky . . . Filtering and reduction . . . . . . Filter . . . . . . . . . . . Filter functions . . . . . . . Generic filter functions . . . Reduce . . . . . . . . . . . Implementing Reduce . . . Other reduction operations . Other considerations . . . . . . . Constraints . . . . . . . . . Concurrency . . . . . . . . Exercise: Compose yourself .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
78 78 79 79 80 81 81 82 83 85 85 85 86 87 87 88 89 89 89 90
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 . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
93 93 94 94 95 95 95 95 96 97 97 98 98 99 99 100
8. Concurrency Data races . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Mutexes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
103 103 104
5
Deadlocks . . . . . . . . . . . . A concurrency‐safe set type . . . . . Locking . . . . . . . . . . . . . The constructor . . . . . . . . . A locking Add method . . . . . The read‐locking methods . . . 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 . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
104 104 105 105 105 106 107 107 107 108 109 110 115 115 116 116
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
117 118 118 118 120 121 121 122 123 123 125 125 126 126 127 127 128 128 128 128 129
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? . . . . . . . Performance . . . . . . . . . . . . . . . . . . . . . . . What impact does generics have on performance? .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
133 133 133 134 135 135 135
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 . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
6
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) .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
136 136 137 138 138 139 139 139 139 140 140 140 141 142 142
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 . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
143 143 144 144 145 145 146 148 148 149 149 150 150 151 151
About this book Who wrote this? . . . . . . . Feedback . . . . . . . . . . . Free updates to future editions Join my Go Club . . . . . . . For the Love of Go . . . . . . The Power of Go: Tools . . . . Further reading . . . . . . . Credits . . . . . . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
153 153 153 154 154 154 155 155 155
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
Acknowledgements
. . . . . . . .
156
7
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
8
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 paramet‐ ers in advance. Instead, some types are represented by placeholders, called type para‐ meters. 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 9
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 to‐ gether 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. 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 ap‐ plies 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
10
• How to define and use constraints on type parameters, and what constraints are provided in standard library 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 dis‐ tribution, you can build it from source or download a suitable binary package from the Go website directly:
• https://go.dev/learn/
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 Git‐ Hub repo.
11
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 vari‐ able 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. 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 12
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 prob‐ lem 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:
func PrintTo(w io.Writer, msg string) { fmt.Fprintln(w, msg) } Here we don’t know what the precise type of the argument w will be at run time (its dy‐ namic 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 defini‐ tion for clues:
type Writer interface { Write(p []byte) (n int, err error) } 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 signa‐ ture (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 spe‐ cifies those methods.
13
For example, we can define some struct type of our own, and give it a Write method that does nothing at all:
type MyWriter struct {} func (MyWriter) Write([]byte) (int, error) { return 0, nil } 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 re‐ quired 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:
PrintTo(MyWriter{}, "Hello, world!") 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 BogusWriter struct{} PrintTo(BogusWriter{}, "This won't compile!") And indeed, we get this error:
cannot use BogusWriter{} (type BogusWriter) as type io.Writer in argument to PrintTo: BogusWriter does not implement io.Writer (missing Write method) That’s fair enough. A function wouldn’t declare a parameter of type io.Writer unless it knew it needed to call Write on that value. By accepting that interface type, it’s saying something about what it plans to do with the parameter: write to it! Go can tell in advance that this won’t work with a BogusWriter, because it doesn’t have any such method. So it won’t let us pass a BogusWriter where an io.Writer is expected.
14
Polymorphism What’s the point of all this, though? Why not just define the PrintTo function to take a MyWriter parameter, for example? That is to say, some concrete (non‐interface) type?
Interfaces make code flexible Well, you already know the answer to that: because more than one concrete type can be an io.Writer. There are many such types in the standard library: for example, *os.File or *bytes.Buffer. A function that takes a io.Writer can work with any of these. Now we can see why interfaces are so useful: they let us write very flexible functions. We don’t have to write multiple versions of the function, like PrintToFile, PrintToBuffer, PrintToBuilder, and so on. Instead, we can write one function that takes an interface parameter, io.Writer, and it’ll work with any type that implements this interface. Indeed, it works with types that don’t even exist yet! As long as it has a Write method, it’ll be acceptable to our func‐ tion. The fancy computer science term for this is polymorphism (“many forms”). But it just means we can take “many types” of value as a parameter, providing they implement some interface (that is, some set of methods) that we specify.
Constraining parameters with interfaces Interfaces in Go are a neat way of introducing some degree of polymorphism into our programs. When we don’t care what type our parameter is, so long as we can call cer‐ tain methods on it, we can use an interface to express that requirement. It doesn’t have to be a standard library interface, such as io.Writer; we can define any interface we want. For example, suppose we’re writing some function that takes a value and turns it into a string, by calling a String method on it. What sort of interface parameter could we take? Well, we know we’ll be calling String on the value, so it must have at least a String method. How can we express that requirement as an interface? Like this:
type Stringer interface { String() string } In other words, any type can be a Stringer so long as it has a String method. Then we can define our Stringify function to take a parameter of this interface type:
15
func Stringify(s Stringer) string { return s.String() } In fact, this interface already exists in the standard library (it’s called fmt.Stringer), but you get the point. By declaring a function parameter of interface type, we can use the same code to handle multiple dynamic types. Note that all we can require about a method using an interface is its name and signa‐ ture (that is, what types it takes and returns). We can’t specify anything about what that method actually does. Indeed, it might do nothing at all, as we saw with the MyWriter type, and that’s okay: it still implements the interface.
Limitations of method sets This “method set” approach to constraining parameters is useful, but fairly limited. Suppose we want to write a function that adds two numbers. We might write something like this:
func AddNumbers(x, y int) int { return x + y } That’s great for int values, but what about float64? Well, we’d have to write essen‐ tially the same function again, but this time with a different parameter and result type:
func AddFloats(x, y float64) float64 { return x + y } The actual logic (x + y) is exactly the same in both cases, so the type system is hurting us more than it’s helping us here. Indeed, we’d also have to write AddInt64s, AddInt32s, AddUints, and so on, and they’d all consist of the same code. This is boring, and it’s not the kind of thing that we became programmers to do. So we need to think of something else. Maybe interfaces can come to our rescue? Let’s try. Suppose we change AddNumbers to take a pair of parameters of some inter‐ face type, instead of a concrete type like int or float64. What interface could we use? In other words, what methods would we need to specify that the parameter type must implement? Well, here’s where we run into a limitation of interfaces defined by method sets. Ac‐ tually, int has no methods, and nor do any of the other built‐in types! So there’s no method set we can specify that would be implemented by int, float64, and friends. 16
We could still define some interface: for example, we could require an Add method, and then we could define struct types with such a method, and pass them to AddNumber. Great. But it wouldn’t allow us to use any of Go’s built‐in number types, and that would be a most inconvenient limitation.
The empty interface: any Here’s another idea. What about the empty interface, named any? That specifies no methods at all, so literally every concrete type implements it. Could we use any as the type of our parameters? Since that would allow us to pass argu‐ ments of any type at all, we might rename our function AddAnything:
// invalid func AddAnything(x, y any) any { return x + y } Unfortunately, this doesn’t compile:
invalid operation: x + y (operator + not defined on interface) The problem here is what we tried to do with the parameters: that is, add them. To do that, we used the + operator, and that’s not allowed here. Why not? Because we said, in effect, that x and y can be any type, and not every type works with the + operator. If x and y were instances of some kind of struct, for example, what would it even mean to add them together? There’s no way to know, so Go plays it safe by disallowing the + operation altogether. And there’s another, subtler problem here, too. We presumably need x and y to be the same concrete type, whatever it is. But because they’re both declared as any, we can call this function with different concrete types for x and y, which almost certainly wouldn’t make sense.
Type assertions and switches You probably know that we can write a type assertion in Go, to detect what concrete type an interface value contains. And there’s a more elaborate construct called a type switch, which lets us detect a whole set of possible types, like this:
switch v := x.(type) { case int: return v + y case float64:
17
return v + y case ... Using a switch statement like this, we can list all the concrete types that we know do support the + operator, and use it with each of them. This seems promising at first, but really we’re just right back where we started! We wanted to avoid writing a separate, essentially identical version of the Add function for each concrete type, but here we are doing basically just that. So an interface is no use here. In practice, we don’t often need to write functions like AddAnything, which is just as well. But this is an awkward limitation of Go, or so it would seem: it makes it difficult for us to write general‐purpose packages, among other things. Look at the math package in the standard library, for example. It provides lots of useful utility functions such as Pow, Abs, and Max… but only on float64 values. If you want to use those functions with some other type, you’ll have to explicitly convert it to float64 on the way in, and back to your preferred type on the way out. That’s just lame.
Go, meet generics This isn’t full generic programming, then, in the way that we now understand the term. We can’t write the equivalent of an AddAnything function using just method‐based interfaces, even the empty interface. Or rather, we can write functions that take values of any type: we just can’t do anything useful with those values, like add them together.
How it started At least, that was true until recently, but now there is a way to do this kind of thing. We can use Go generics! We’ll see in the next chapter what that actually involves, but let’s take a very brief trip through the history of Go to see how we got where we are today. Go was released on November 10, 2009. Less than 24 hours later we saw the first comment about generics. —Ian Lance Taylor, “Why Generics?” Go was deliberately designed to be a very simple language, and also to make it easy to compile and build Go programs very fast, without using a lot of resources. That means it doesn’t have everything, and generics is one of the features Go didn’t have at launch. Why didn’t the designers just add generics later, then? Well, there are a couple of com‐ pelling reasons. 18
How it’s going Go was intended to be quick to learn, without a lot of syntax and keywords to master before you can be productive with the language. Every new thing you add to it is some‐ thing else that beginners will have to learn. The Go team also puts a great value on backwards compatibility: that is to say, no break‐ ing changes can be introduced to the language. So if you introduce some new syntax, it has to be done in a way that doesn’t conflict with any possible existing programs. That’s hard! Various proposals for generics in Go have been made over the years, in fact, but most of them fell at one or another of these hurdles. Some involved an unacceptable hit to com‐ piler performance, or to runtime performance; others introduced too much complexity or weren’t backwards compatible with existing code. That’s why it took about ten years for generics to finally land in Go. What we have, after all that thinking and arguing, is actually a very nice design. Like Go itself, it does a lot with a little. With the absolute minimum of new syntax, the Go team have opened up a whole new world of programming, and enabled us to write new kinds of programs in Go. It’ll take a while for the consequences of all this to feed through into the mainstream, and for most people, even for experienced Gophers, generics are something very new. In the next chapter, we’ll talk about exactly what it is that’s been added to Go, and start writing some generic programs of our own.
19
2. Type parameters I learned very early the difference between knowing the name of something, and knowing something. —Richard Feynman, “The World From Another Point of View”
Now that we’ve described what generic programming is, and how it came to Go, it’s time to look at exactly what Go’s generics support involves, and what we can do with it.
Generic functions And now that’s easier to explain: it means we can write functions like PrintAnything and AddAnything!
Introducing T, the arbitrary type To be precise, we can write generic functions that take parameters not of some specific named type, but of some arbitrary type that we don’t have to specify in advance. Let’s call it T, for “type”. When the function is actually called in our program, T will be some specific type, such
20
as int. But we don’t want to have to specify that in advance when we’re writing the function, so we’re just going to use T as a placeholder for whatever the type ends up being. In fact, there might be more than one T in our finished program. For example, we might call our generic function with a float64 value, in which case T will be float64. But elsewhere we might call the same function with a string value, in which case T will be string. This T placeholder is called a type parameter. A generic function in Go, then, is a func‐ tion that takes a type parameter. So what does that look like? Let’s take the simplest imaginable case first. We’ll write a PrintAnything function with a type parameter T, where T is a placeholder for any type:
func PrintAnything[T any](v T) {
Type parameters What does this mean? For any type T, PrintAnything[T] takes a T parameter (that is, a parameter whose type is T), and returns nothing. The function signature just says that in Go instead of English. While v is just an ordinary parameter, of some unspecified type, T is different. T is a new kind of parameter in Go: a type parameter. We say that PrintAnything is a parameterised function, that is, a generic function on some type T. For short, we usually just talk about PrintAnything[T], pronounced “PrintAnything of T”.
Instantiation What type is T, specifically? It depends what we decide to pass to the function when it’s called. Suppose we want to pass it an int value, for example:
var x int = 5 PrintAnything(x) // Output: // 5 Go is smart enough to know that x is an int, and therefore it needs to compile (and call) a version of PrintAnything[T] where T is int. This is called instantiating the function, as in “creating an instance of it”. In effect, the generic PrintAnything function is like a kind of template, and when we call it with some specific type, we create a specific instance of the function that takes that type—for example, int.
21
What if we have another call to PrintAnything[T] somewhere else in the program, and this time T is a different type, such as string? Well, that’s okay. Go will produce another version of PrintAnything, this time one that takes a string argument. In most cases, as in these examples, Go can infer the type of T from the value that’s sup‐ plied at the site of the function call. When that isn’t possible, Go will let you know. For example, suppose we have a generic function Something[T any] that returns a value of T. And suppose we call it like this:
x := Something() What is T here? We don’t know, and nor does Go, so it complains:
cannot infer T All is not lost, however. We can specify which particular T we want in this case by put‐ ting it inside square brackets after the function name:
x := Something[int]() It’s always okay to explicitly instantiate a parameterised function or type in this way. But you only have to do it when Go can’t automatically infer the required type, which isn’t all that often.
Stencilling This approach to implementing generics is sometimes called stencilling, which is a rather apt name. You can imagine Go spray‐painting a bunch of similar versions of the function, differing only in the type of the parameter they take. We could have done the same thing ourselves using the existing code generation ma‐ chinery in Go, and indeed many people did do exactly that before the introduction of generics. This makes for efficient machine code, because there’s no indirection (unlike with in‐ terface values). We don’t need type assertions, because each different implementation of PrintAnything knows exactly what concrete type it’s getting. This isn’t a particularly compelling example of generics in action, though, because it was already possible to write PrintAnything using a method‐set interface: any. We could just pass the argument straight to fmt.Println, which also takes any.
Getting started Let’s look at a slightly more interesting, though still rather contrived, example.
22
An Identity function Suppose we want to write a function called Identity that simply returns whatever value you pass it. (I know it doesn’t sound very useful, but bear with me.) How could we write such a function in Go, without having to implement a separate version for each possible type of value? This is where we start to go beyond the limits of interfaces. Using any, for example, we’d have to write something like:
func Identity(v any) any { return v } This works, but it isn’t really satisfactory. As we saw with AddAnything in the previous chapter, we don’t have any way to tell Go that the function’s parameter and its result must be the same concrete type, whatever it is. And clearly that needs to be the case here: if we pass this function an int, we expect to get an int back. Now we know how to specify that requirement, using a type parameter:
func Identity[T any](v T) T { return v } Notice that, although it looks similar to the non‐generic version, there’s an important difference. Whatever T turns out to be (and it can be any type), both the parameter and the function’s result must be of that type.
Instantiating Identity Suppose we call this function somewhere in our program with a string argument, then:
fmt.Println(Identity("Hello")) // Output: // Hello You now know how this works. Under the hood, Go instantiates a version of Identity that takes a string parameter and returns a string result. This is just a plain, ordinary Go function that we could have written ourselves, or generated mechanically. The point is, of course, that we don’t need to supply a separate version of Identity for each concrete type that we want to use. Instead, we just write it once for some arbitrary type T, and Go will automatically generate a version of Identity for each type that’s actually used in our program.
23
Exercise: Hello, generics Now it’s over to you to write your first generic function in Go! Let’s work through it to‐ gether, step by step. First of all, make sure you’ve checked out a copy of the GitHub repo for this book:
• https://github.com/bitfield/know‐go Open the exercises/print folder in your code editor and take a look at the print_test.go file. You’ll find this test:
func TestPrintAnythingTo_PrintsToGivenWriter(t *testing.T) { t.Parallel() buf := new(bytes.Buffer) print.PrintAnythingTo(buf, "Hello, world") want := "Hello, world\n" got := buf.String() if want != got { t.Errorf("want %q, got %q", want, got) } } (Listing exercises/print)
Running the test Run the test using your editor, or the go test command. You’ll see that right now the test doesn’t compile, because the required function doesn’t exist:
undefined: print.PrintAnythingTo Remember, you’ll need at least Go version 1.18 to be able to use generics, including run‐ ning this test and implementing the function to make it pass. That’s because we’re us‐ ing some new syntax that doesn’t exist in Go version 1.17 and earlier. If you try to compile this code with an older version of Go that doesn’t support generics, you’ll get a rather confusing additional error message:
type string is not an expression So if you see this, you need to upgrade your Go. Follow the instructions in the introduc‐ tion to this book to do that. Now read on! GOAL: Get the test passing!
24
HINT: To make this test even compile, you’ll need to define a generic function in the print package named PrintAnythingTo that takes one parameter of type io.Writer, and another value that’s of some unspecified type. In other words, PrintAnythingTo has a type parameter we’ll refer to as T, which can be any type, and it takes a parameter of this type T, just like Identity. But unlike Identity, it also takes another parameter, which is of type io.Writer. To make the test pass, your function will need to write the supplied value to the supplied writer. It’s up to you how to do this, but you might like to use fmt.Fprintln, like our PrintTo example in the previous chapter. The necessary go.mod and print.go files are already set up for you. All you need to do is edit print.go and add the PrintAnythingTo function, then run the test again. SOLUTION: Here’s my suggested solution; it’s fine if yours looks different, so long as it passes the test. It’s just for comparison, or if you need a little extra help on this tricky first exercise.
package print import ( "fmt" "io" ) func PrintAnythingTo[T any](w io.Writer, p T) { fmt.Fprintln(w, p) } (Listing solutions/print) We use the [T any] syntax to say that PrintAnything is about some arbitrary type T, and it takes a parameter—p, the thing to print—of that type, whatever it actually turns out to be. In the test, as it happens, that’s a string, but it could have been any type. If you like, you can add more tests that call PrintAnything with different types, such as int or float64.
Composite types So far, we’ve figured out how to define a generic function that, for some type T, takes a parameter of type T:
func Identity[T any](v T) {
25
So is that it? Are we restricted to declaring only parameters of type T itself, or could we also take some composite type? That is, some type involving T, not just T itself? For example, a slice of T?
Slices of some arbitrary type We could indeed. Suppose we wanted to write a function Len that returns the length of a given slice. And its parameter will always be a slice of something: that is, of some arbitrary element type. Let’s call it E, for “element”. The signature of Len, then, might look something like this:
func Len[E any](s []E) int { It’s conventional, though not required, to capitalise the names of type parameters. Those names can be whatever you like, but again it’s conventional to use a single letter: T for any type, E for an element type of a slice, and so on.
Other generic composite types But we can also use a type parameter in other kinds of composite type. For example, we can write a generic function on a channel of some element type E:
func Drain[E any](ch