📄 Page
1
(This page has no text content)
📄 Page
2
Contents Praise for ‘Know Go’ 8 Introduction 9 About the book . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 About generics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 Who is the book for? . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 What should I read first? . . . . . . . . . . . . . . . . . . . . . . . . . 10 What will I learn from this book? . . . . . . . . . . . . . . . . . . . . . 10 Completing the challenges . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 How do I install the Go tools? . . . . . . . . . . . . . . . . . . . . . . . 11 Where are the code examples? . . . . . . . . . . . . . . . . . . . . . . 11 1. Interfaces 12 Programming with types . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 Specific programming . . . . . . . . . . . . . . . . . . . . . . . . . . 12 Generic programming . . . . . . . . . . . . . . . . . . . . . . . . . . 13 Interface types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 Interface parameters . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 Polymorphism . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 Interfaces make code flexible . . . . . . . . . . . . . . . . . . . . . . . 15 Constraining parameters with interfaces . . . . . . . . . . . . . . . . . 15 Limitations of method sets . . . . . . . . . . . . . . . . . . . . . . . . 16 The empty interface: any . . . . . . . . . . . . . . . . . . . . . . . . . 17 Type assertions and switches . . . . . . . . . . . . . . . . . . . . . . . 18 Go, meet generics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 How it started . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 How it’s going . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 2. Type parameters 20 Generic functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 Introducing T, the arbitrary type . . . . . . . . . . . . . . . . . . . . . 20 Type parameters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 Instantiation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 Stencilling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 Getting started . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 An Identity function . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 Instantiating Identity . . . . . . . . . . . . . . . . . . . . . . . . . . 23 Exercise: Hello, generics . . . . . . . . . . . . . . . . . . . . . . . . . 24 Running the test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 2
📄 Page
3
Composite types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 Slices of some arbitrary type . . . . . . . . . . . . . . . . . . . . . . . 26 Other generic composite types . . . . . . . . . . . . . . . . . . . . . . 26 Generic types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 Defining a generic slice type . . . . . . . . . . . . . . . . . . . . . . . 27 The elements all have the same type . . . . . . . . . . . . . . . . . . . 27 Generic types need to be instantiated . . . . . . . . . . . . . . . . . . . 28 Exercise: Group therapy . . . . . . . . . . . . . . . . . . . . . . . . . 28 Generic function types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 Generic functions as values . . . . . . . . . . . . . . . . . . . . . . . . 29 The type is always instantiated . . . . . . . . . . . . . . . . . . . . . . 29 There are no generic functions . . . . . . . . . . . . . . . . . . . . . . 30 Generic types as function parameters . . . . . . . . . . . . . . . . . . . . . 31 Exercise: Lengthy proceedings . . . . . . . . . . . . . . . . . . . . . . 31 Constraining type parameters . . . . . . . . . . . . . . . . . . . . . . . . . 32 We can’t add any to any . . . . . . . . . . . . . . . . . . . . . . . . . . 32 Not every type is “addable” . . . . . . . . . . . . . . . . . . . . . . . . 33 3. Constraints 34 Method set constraints . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 Limitations of the any constraint . . . . . . . . . . . . . . . . . . . . . 34 Basic interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 Exercise: Stringy beans . . . . . . . . . . . . . . . . . . . . . . . . . . 36 Type set constraints . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 Type elements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 Using a type set constraint . . . . . . . . . . . . . . . . . . . . . . . . 37 Unions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 The set of all allowed types . . . . . . . . . . . . . . . . . . . . . . . . 39 Intersections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 Empty type sets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 Composite type literals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 A struct type literal . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 Access to struct fields . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 Some limitations of type sets . . . . . . . . . . . . . . . . . . . . . . . . . . 41 Constraints versus basic interfaces . . . . . . . . . . . . . . . . . . . . 41 Constraints are not classes . . . . . . . . . . . . . . . . . . . . . . . . 42 Approximations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 Limitations of named types . . . . . . . . . . . . . . . . . . . . . . . . 43 Type approximations . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 Derived types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 Exercise: A first approximation . . . . . . . . . . . . . . . . . . . . . . 45 Interface literals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 Syntax of an interface literal . . . . . . . . . . . . . . . . . . . . . . . 46 Omitting the interface keyword . . . . . . . . . . . . . . . . . . . . . 47 Referring to type parameters . . . . . . . . . . . . . . . . . . . . . . . 48 Exercise: Greater love . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 3
📄 Page
4
4. Operations 51 Arithmetic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51 An AddAnything function . . . . . . . . . . . . . . . . . . . . . . . . . 52 The set of all numeric types . . . . . . . . . . . . . . . . . . . . . . . . 52 Exercise: Product placement . . . . . . . . . . . . . . . . . . . . . . . 53 Ordered types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 The > operator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 An Ordered interface . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 The cmp.Ordered constraint . . . . . . . . . . . . . . . . . . . . . . . 57 Multiple type parameters . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58 Function on two (or more) types . . . . . . . . . . . . . . . . . . . . . 58 Each type parameter is a distinct type . . . . . . . . . . . . . . . . . . 58 Functions on slice types . . . . . . . . . . . . . . . . . . . . . . . . . 59 The problem with derived slice types . . . . . . . . . . . . . . . . . . . 60 Parameterizing by slice and element type . . . . . . . . . . . . . . . . 61 And I need to know this because…? . . . . . . . . . . . . . . . . . . . . 61 Comparable types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 Not every type is comparable . . . . . . . . . . . . . . . . . . . . . . . 62 Not every comparable type is ordered . . . . . . . . . . . . . . . . . . 62 There are infinitely many comparable types . . . . . . . . . . . . . . . 63 The comparable constraint . . . . . . . . . . . . . . . . . . . . . . . . 63 Why is comparable predeclared? . . . . . . . . . . . . . . . . . . . . . 64 Exercise: Duplicate keys . . . . . . . . . . . . . . . . . . . . . . . . . 64 Abstract types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66 A Greatest function . . . . . . . . . . . . . . . . . . . . . . . . . . . 66 The scope of type parameters . . . . . . . . . . . . . . . . . . . . . . . 67 The zero value . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68 Switching on abstract types . . . . . . . . . . . . . . . . . . . . . . . . 68 5. Types 71 Named types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 Named basic types . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 Generic basic types . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 Generic slice types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 There are no generic types . . . . . . . . . . . . . . . . . . . . . . . . 73 Slices of interface types . . . . . . . . . . . . . . . . . . . . . . . . . . 74 Generic map types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74 Maps of abstract element types . . . . . . . . . . . . . . . . . . . . . . 74 Multiple type parameters . . . . . . . . . . . . . . . . . . . . . . . . . 75 Instantiating multiple type parameters . . . . . . . . . . . . . . . . . . 76 Generic struct types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76 Self‐reference . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76 Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 Adding methods to generic types . . . . . . . . . . . . . . . . . . . . . 77 Exercise: Empty promises . . . . . . . . . . . . . . . . . . . . . . . . 78 4
📄 Page
5
Parameterised methods . . . . . . . . . . . . . . . . . . . . . . . . . . 79 More generic composite types . . . . . . . . . . . . . . . . . . . . . . . . . 79 Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79 Channels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80 6. Functions 81 Functions on container types . . . . . . . . . . . . . . . . . . . . . . . . . 81 Contains . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82 Reverse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82 Sort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83 First‐class functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84 Map . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84 Type inference . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85 Exercise: Func to funky . . . . . . . . . . . . . . . . . . . . . . . . . . 86 Filtering and reduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88 Filter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88 Filter functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89 Generic filter functions . . . . . . . . . . . . . . . . . . . . . . . . . . 89 Reduce . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90 Implementing Reduce . . . . . . . . . . . . . . . . . . . . . . . . . . . 91 Other reduction operations . . . . . . . . . . . . . . . . . . . . . . . . 91 Other considerations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92 Constraints . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92 Concurrency . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93 Exercise: Compose yourself . . . . . . . . . . . . . . . . . . . . . . . . 93 7. Containers 97 Sets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97 Maps as sets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98 Operations on sets . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98 Designing the Set type . . . . . . . . . . . . . . . . . . . . . . . . . . 99 Building out the machinery . . . . . . . . . . . . . . . . . . . . . . . . . . 99 The Addmethod . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99 The Containsmethod . . . . . . . . . . . . . . . . . . . . . . . . . . 100 Initialising with multiple values . . . . . . . . . . . . . . . . . . . . . 100 Getting a set’s members . . . . . . . . . . . . . . . . . . . . . . . . . . 101 A Stringmethod . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102 Logic on sets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102 Union . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102 Intersection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103 A real‐life example . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104 Exercise: Stack overflow . . . . . . . . . . . . . . . . . . . . . . . . . 104 8. Concurrency 108 Data races . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108 Mutexes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109 5
📄 Page
6
Deadlocks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109 A concurrency‐safe set type . . . . . . . . . . . . . . . . . . . . . . . . . . 109 Locking . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110 The constructor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110 A locking Addmethod . . . . . . . . . . . . . . . . . . . . . . . . . . . 110 The read‐locking methods . . . . . . . . . . . . . . . . . . . . . . . . 111 Testing concurrency safety . . . . . . . . . . . . . . . . . . . . . . . . . . . 112 Smoke tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112 A fatal error . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113 The race detector . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113 And the rest . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114 Exercise: Channelling frustration . . . . . . . . . . . . . . . . . . . . . 115 Extending the Set type . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120 More set operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121 Key‐value stores and caches . . . . . . . . . . . . . . . . . . . . . . . 121 Other container types . . . . . . . . . . . . . . . . . . . . . . . . . . . 121 9. Packages 123 The cmp package . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124 The slices package . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124 Comparing slices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124 Finding elements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126 Maxima and minima . . . . . . . . . . . . . . . . . . . . . . . . . . . 127 Inserting and deleting . . . . . . . . . . . . . . . . . . . . . . . . . . 128 Cloning and compacting . . . . . . . . . . . . . . . . . . . . . . . . . 128 Growing and shrinking . . . . . . . . . . . . . . . . . . . . . . . . . . 129 Sorting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130 Reversing and replacing . . . . . . . . . . . . . . . . . . . . . . . . . 131 Searching . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132 The maps package . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133 Comparing maps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133 Deleting map entries . . . . . . . . . . . . . . . . . . . . . . . . . . . 133 Cloning and copying . . . . . . . . . . . . . . . . . . . . . . . . . . . 134 New idioms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135 Copying slices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135 Deleting slice elements . . . . . . . . . . . . . . . . . . . . . . . . . . 135 Checking for slice elements . . . . . . . . . . . . . . . . . . . . . . . . 135 Exercise: Merging in turn . . . . . . . . . . . . . . . . . . . . . . . . . . . 136 10. Questions 140 Change . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140 Howmuch do I need to know about generics? . . . . . . . . . . . . . . 140 Will generics drastically change the way I write Go? . . . . . . . . . . . 141 Do I need to change my existing code? . . . . . . . . . . . . . . . . . . 142 Performance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142 What impact does generics have on performance? . . . . . . . . . . . . 142 6
📄 Page
7
Does generic code compile more slowly? . . . . . . . . . . . . . . . . . 143 Downsides . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143 Why didn’t they use angle brackets? . . . . . . . . . . . . . . . . . . . 144 Still no option types . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145 Still no enums . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145 No proper union types . . . . . . . . . . . . . . . . . . . . . . . . . . 146 No macros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146 No parameterised methods . . . . . . . . . . . . . . . . . . . . . . . . 146 No generic packages . . . . . . . . . . . . . . . . . . . . . . . . . . . 147 No “insert cool tech here” . . . . . . . . . . . . . . . . . . . . . . . . . 147 More change . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147 Will there be a “Go 2”? . . . . . . . . . . . . . . . . . . . . . . . . . . 148 There will be changes to Go . . . . . . . . . . . . . . . . . . . . . . . . 148 It won’t be “Go 2” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149 But there will be a successor to Go (someday) . . . . . . . . . . . . . . . 149 11. Iterators 151 Introduction to iterators . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151 Why are iterators useful? . . . . . . . . . . . . . . . . . . . . . . . . . 152 The signature of an iterator function . . . . . . . . . . . . . . . . . . . 152 Using iterators in programs . . . . . . . . . . . . . . . . . . . . . . . . . . 153 Single‐value iterators: iter.Seq . . . . . . . . . . . . . . . . . . . . . 154 Two‐value iterators: iter.Seq2 . . . . . . . . . . . . . . . . . . . . . . 155 Dealing with errors . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156 Cleaning up . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157 Embracing iterators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157 Composing iterators . . . . . . . . . . . . . . . . . . . . . . . . . . . 157 When iterators beat channels . . . . . . . . . . . . . . . . . . . . . . . 158 Standard library changes . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159 Slices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159 Maps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160 About this book 162 Who wrote this? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162 Feedback . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162 Free updates to future editions . . . . . . . . . . . . . . . . . . . . . . . . . 163 Join my Go Club . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163 For the Love of Go . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163 The Power of Go: Tools . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164 Further reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164 Credits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164 Acknowledgements 165 7
📄 Page
8
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
📄 Page
9
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 param‐ eters in advance. Instead, some types are represented by placeholders, called type pa- rameters. 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
📄 Page
10
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
📄 Page
11
• 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 GitHub repo. 11
📄 Page
12
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. 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
📄 Page
13
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 func‐ tion 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 par‐ ticular 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 Writemethod implements it automatically. 13
📄 Page
14
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 speci‐ fies those methods. For example, we can define some struct type of our own, and give it a Writemethod 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 Writemethod 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 Writemethod. 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 Writemethod 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 14
📄 Page
15
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. 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, PrintTo- Buffer, 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 Writemethod, it’ll be acceptable to our function. 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 Stringmethod 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: 15
📄 Page
16
type Stringer interface { String() string } In other words, any type can be a Stringer so long as it has a Stringmethod. Then we can define our Stringify function to take a parameter of this interface type: 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 essentially 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 16
📄 Page
17
all consist of the same code. This is boring, and it’s not the kind of thing that we be‐ came 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 interface 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. We could still define some interface: for example, we could require an Addmethod, 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 17
📄 Page
18
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: 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 in‐ terfaces, 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. 18
📄 Page
19
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. 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
📄 Page
20
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