Generics in Go
Why Generics?
To write DRY code! Generics allows us to use variable to refer to specific types. This helps us reduce code duplication.
How To Use Generics
Define a variable with the constraint any
. The variable does not have to be named T
.
func yourFunc[T any](s []T) ([]T) {
// your code here
}
From this example, we’ve defined T
to constraint any
and use it to define both the argument and return type to []T
. Which means a slice of any
type.
Constraints
Constraints are interfaces that allow us to write generics that only operate within the constraints of a given interface type. This means that any interface we define could possible be used as a generic constraint. The any
constraint is the same as a empty interface which will match anything.
Create Custom Constraints
The concat
function takes a slice of values and concatenates it into a string. This should work with any type that can represent itself as a string.
type stringer interface {
String() string
}
func concat[T stringer](vals []T) string {
result := ""
for _, val := range vals {
// this is where the .String() method
// is used. That's why we need a more specific
// constraint instead of the any constraint
result += val.String()
}
return result
}
Interface Type Lists
We can also create interfaces with just a list of types.
type Number interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 |
~float32 | ~float64
}
Parametic Constraints
Interface definitions can accept type parameters as well.
type store[P product] interface {
Sell(P)
}
type product interface {
Price() float64
Name() string
}
type guitar struct {
name string
manufacturer string
price float64
}
func (g guitar) Name() string {
return fmt.Sprintf("%s by %s", b.name, b.manufacturer)
}
func (g guitar) Price() float64 {
return g.price
}
type guitarStore struct {
guitarsSold []guitar
}
func (gs *guitarStore) Sell(g guitar) {
gs.guitarsSold = append(gs.guitarsSold, g)
}