Introduction
Generics in Go allow you to write flexible and reusable functions and data structures that can operate on different types without sacrificing type safety. Introduced in Go 1.18, generics enable you to define functions, methods, and types that are parameterized by type. In this chapter, you will learn the basics of using generics in Go, including defining generic functions and types, and common use cases.
Defining Generic Functions
Generic functions use type parameters to operate on different types. Type parameters are specified within square brackets ([]
) after the function name.
Example: Generic Function for Swapping Values
package main
import "fmt"
// swap is a generic function that swaps two values of any type.
func swap[T any](a, b T) (T, T) {
return b, a
}
func main() {
x, y := 1, 2
x, y = swap(x, y) // Swap integers
fmt.Println(x, y) // Output: 2 1
a, b := "hello", "world"
a, b = swap(a, b) // Swap strings
fmt.Println(a, b) // Output: world hello
}
In this example, the swap
function uses a type parameter T
to swap values of any type.
Defining Generic Types
Generic types allow you to create data structures that can hold values of different types. Type parameters are specified within square brackets ([]
) after the type name.
Example: Generic Stack
package main
import "fmt"
// Stack is a generic type that represents a stack of items of any type.
type Stack[T any] struct {
items []T
}
// Push adds an item to the top of the stack.
func (s *Stack[T]) Push(item T) {
s.items = append(s.items, item)
}
// Pop removes and returns the item from the top of the stack. If the stack is empty, it returns the zero value of the type and false.
func (s *Stack[T]) Pop() (T, bool) {
if len(s.items) == 0 {
var zero T
return zero, false
}
item := s.items[len(s.items)-1]
s.items = s.items[:len(s.items)-1]
return item, true
}
func main() {
intStack := Stack[int]{}
intStack.Push(1)
intStack.Push(2)
fmt.Println(intStack.Pop()) // Output: 2 true
fmt.Println(intStack.Pop()) // Output: 1 true
fmt.Println(intStack.Pop()) // Output: 0 false
stringStack := Stack[string]{}
stringStack.Push("hello")
stringStack.Push("world")
fmt.Println(stringStack.Pop()) // Output: world true
fmt.Println(stringStack.Pop()) // Output: hello true
fmt.Println(stringStack.Pop()) // Output: false
}
In this example, the Stack
type uses a type parameter T
to hold items of any type.
Constraints
Type parameters can have constraints, which restrict the types that can be used with a generic function or type. Constraints are specified using interfaces.
Example: Generic Function with Constraints
package main
import "fmt"
// Adder is an interface constraint that allows only int and float64 types.
type Adder interface {
int | float64
}
// add is a generic function that adds two values of a type that implements the Adder interface.
func add[T Adder](a, b T) T {
return a + b
}
func main() {
fmt.Println(add(1, 2)) // Output: 3
fmt.Println(add(1.5, 2.3)) // Output: 3.8
// fmt.Println(add("a", "b")) // Compile-time error: string does not implement Adder
}
In this example, the Adder
interface is used as a constraint to ensure that the add
function can only be used with int
and float64
types.
Using Multiple Type Parameters
You can use multiple type parameters in generic functions and types.
Example: Generic Function with Multiple Type Parameters
package main
import "fmt"
// pair is a generic function that returns a pair of values of any types.
func pair[A, B any](a A, b B) (A, B) {
return a, b
}
func main() {
// Pairing an integer with a string
a, b := pair(1, "hello")
fmt.Println(a, b) // Output: 1 hello
// Pairing a float with a boolean
x, y := pair(1.5, true)
fmt.Println(x, y) // Output: 1.5 true
}
In this example, the pair
function uses two type parameters A
and B
to return a pair of values of any types.
Conclusion
Generics in Go provide a powerful way to write flexible and reusable code. By understanding how to define and use generic functions and types, as well as how to apply constraints, you can create more versatile and type-safe programs. This guide covers the basics, but there are many more advanced use cases and patterns you can explore as you become more familiar with Go’s generics.