Author:John Arundel
No description
AI Reading Assistant
Summary and highlights from this book's index; jump to passages in the text
Tags
Support Statistics
¥.00 ·
0times
Text Preview (First 20 pages)
Registered users can read the full content for free
Register as a Gaohf Library member to read the complete e-book online for free and enjoy a better reading experience.
Page
1
(This page has no text content)
Page
2
Know Go John Arundel Bitfield Consulting February 13, 2026 © 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 Getting started An Identity function
Page
4
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 Access to struct fields Some limitations of type sets Constraints versus basic interfaces
Page
5
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 Why is comparable predeclared? Exercise: Duplicate keys Abstract types A Greatest function
Page
6
The scope of type parameters The zero value Switching on abstract types 5. Types Named types Named basic types Generic basic types Generic type aliases 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 Filtering and reduction Filter Filter functions Generic filter functions
Page
7
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 Testing concurrency safety Smoke tests A fatal error The race detector And the rest
Page
8
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? 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
Page
9
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 Get my free newsletter Free updates to future editions The Deeper Love of Go The Power of Go: Tools Further reading Credits Acknowledgements
Page
10
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
11
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 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
Page
12
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. Before we start, though, please take a moment to subscribe to my newsletter —it’s free, and it’s also the best way for you to stay up to date with my latest books, tutorials, and blog posts: https://bitfieldconsulting.com/subscribe 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.
Page
13
What should I read first? If you’re completely new to Go, or even to programming, I recommend you read my introductory book, The Deeper 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 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.
Page
14
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/ 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
15
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,
Page
16
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: 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. func PrintTo(w io.Writer, msg string) { fmt.Fprintln(w, msg) }
Page
17
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: 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 type Writer interface { Write(p []byte) (n int, err error) } type MyWriter struct {} func (MyWriter) Write([]byte) (int, error) { return 0, nil }
Page
18
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: 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. PrintTo(MyWriter{}, "Hello, world!") type BogusWriter struct{} PrintTo(BogusWriter{}, "This won't compile!")
Page
19
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 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 certain 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.
Page
20
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: 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: 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 signature (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 type Stringer interface { String() string } func Stringify(s Stringer) string { return s.String() }
Comments 0
Loading comments...
Reply to Comment
Edit Comment