
- Go - Home
- Go - Overview
- Go - Environment Setup
- Go - Program Structure
- Go - Basic Syntax
- Go - Data Types
- Go - Variables
- Go - Constants
- Go - Identifiers
- Go - Keywords
- Go - Operators
- Go - Arithmetic Operators
- Go - Assignment Operators
- Go - Relational Operators
- Go - Logical Operators
- Go - Bitwise Operators
- Go - Miscellaneous Operators
- Go - Operators Precedence
- Go Decision Making
- Go - Decision Making
- Go - If Statement
- Go - If Else Statement
- Go - Nested If Statements
- Go - Switch Statement
- Go - Select Statement
- Go Control Flow Statements
- Go - For Loop
- Go - Nested for Loops
- Go - Break Statement
- Go - Continue Statement
- Go - Goto Statement
- Go Functions
- Go - Functions
- Go - Call by Value
- Go - Call by Reference
- Go - Functions as Values
- Go - Function Closure
- Go - Function Method
- Go - Anonymous function
- Go Strings
- Go - Strings
- Go - String Length
- Go - String Concatenation
- Go - Compare Strings
- Go - Split String
- Go - Substring Extraction
- Go - String Replacement
- Go - String Interpolation
- Go - Parse Date Strings
- Go Arrays
- Go - Arrays
- Go - Multidimensional Arrays
- Go - Multidimensional Arrays
- Go - Passing Arrays to Functions
- Go - Pointers
- Go - Pointers
- Go - Array of pointers
- Go - Pointer to pointer
- Go - Passing pointers to functions
- Go Advanced Control Structures
- Go - Scope Rules
- Go - Dereferencing Pointer
- Go - Structures
- Go - Slice
- Go - Slice of Slices
- Go - Range
- Go - Maps
- Go - Recursion
- Go - Type Casting
- Go - Interfaces
- Go - Type Assertion
- Go - Error Handling
- Go - Concurrency
- Go - Regular Expression
- Go - Inheritance
- Go - Packages
- Go - Templates
- Go - Reflection
- Go - Generics
- Go File Handling
- Go - Read File By Word
- Go - Read File By Line
- Go - Read CSV Files
- Go - Delete File
- Go - Rename & Move File
- Go - Truncate a File
- Go - File Read-Write Mode W/O Truncation
- Go Miscellaneous
- Go - defer Keyword
- Go - Fmt Package
- Go - Zero Value
- Go - Import
Go - Generics
Generics came to Go 1.18 (March 2022) to support writing flexible, reusable code that is applicable with several types. Prior to generics, Go used interfaces or code duplication for similar use cases. It enables you to declare functions, structs, and interfaces that can work with type parameters.
Go 1.18+ release brought major features such as generics, fuzz testing, and performance enhancements, increasing flexibility and developer productivity.
Go can usually infer type parameters, so you don't have to declare them. The Generics are implemented at compile time, so they don't have runtime overhead like reflection. But overuse of generics can cause code bloat because of monomorphization (each type instantiation creates different code).
We can utilize this chapter for Data structures (such as stacks, queues, trees), Utility functions (such as Map, Filter, Reduce) and Algorithms (such as sorting, searching).
Type Parameters
Type parameters in Go allow you to define functions, structs, or interfaces that can operate on multiple types, specified using square brackets []
func Print[T any](value T) { ... }
Example
This program demonstrates the use of generics in Go by defining a generic function, 'PrintString', that prints the string representation of any type implementing the 'Stringer' interface.
package main import "fmt" type Stringer interface { String() string } func PrintString[T Stringer](value T) { fmt.Println(value.String()) } type MyType struct { data string } func (m MyType) String() string { return m.data } func main() { val := MyType{data: "Hello, Generics!"} PrintString(val) }
It will generate the following output −
Hello, Generics!
Constraints
The Constraints is used to specify what types are allowed for a type parameter. Here, we use predefined constraints like any (any type) or comparable (types that support == and !=) and also we can define custom constraints using interfaces.
Example
The program demonstrates generics in Go by defining a function 'Add' that can add two numbers of any numeric type (integer or float) using type constraints.
package main import ( "fmt" "golang.org/x/exp/constraints" ) // Add numbers of any numeric type func Add[T constraints.Integer | constraints.Float](a, b T) T { return a + b } func main() { fmt.Println(Add(1, 2)) fmt.Println(Add(1.5, 2.5)) }
We define the 'Ordered' constraint manually because the 'constraints' package is unavailable in Go versions before 1.21.
package main import "fmt" type Ordered interface { int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | uintptr | float32 | float64 | string } func Add[T Ordered](a, b T) T { return a + b } func main() { fmt.Println(Add(1, 2)) fmt.Println(Add(1.5, 2.5)) }
It will generate the following output −
3 4
The first program fails because it depends on the 'constraints' package, which is not part of the standard library in Go versions before 1.21. The second program solves this by defining a custom 'Ordered' constraint, making it compatible with Go 1.18 and later.
Generic Functions
The Generic Functions are the functions that can operate on multiple types.
Example
The program demonstrates a generic function 'Print' that can print values of any type using Go's 'any' type parameter.
package main import "fmt" // Print any type func Print[T any](value T) { fmt.Println(value) } func main() { Print(42) Print("Hello") Print(3.14) }
It will generate the following output −
42 Hello 3.14
Generic Types
The Structs, slices, or other types that can work with type parameters is called as generic types.
Example
The program defines a generic stack ('Stack[T]') that can store and retrieve elements of any type ('T'), demonstrated by pushing and popping integers and strings.
package main import "fmt" type Stack[T any] struct { items []T } func (s *Stack[T]) Push(item T) { s.items = append(s.items, item) } func (s *Stack[T]) Pop() T { if len(s.items) == 0 { panic("stack is empty") } item := s.items[len(s.items)-1] s.items = s.items[:len(s.items)-1] return item } func main() { intStack := Stack[int]{} intStack.Push(1) intStack.Push(2) fmt.Println(intStack.Pop()) stringStack := Stack[string]{} stringStack.Push("Go") stringStack.Push("Generics") fmt.Println(stringStack.Pop()) }
It will generate the following output −
2 Generics
You can't define generic methods, only generic functions and types. Overusing generics can make code harder to read and understand. The Go standard library has limited support for generics as of Go 1.18+.
To Learn more about this chapter, below is a list of potential additions and clarifications −
1. Type Lists in Constraints
Before Go 1.18, constraints were defined using type lists in interfaces. In Go 1.18+, the golang.org/x/exp/constraints package provides common constraints like Integer, Float, Ordered, etc.
package main import ( "fmt" "golang.org/x/exp/constraints" ) // Max function using constraints.Ordered func Max[T constraints.Ordered](a, b T) T { if a > b { return a } return b } func main() { fmt.Println("Max(3, 5):", Max(3, 5)) fmt.Println("Max(3.14, 2.71):", Max(3.14, 2.71)) }
It will generate the following output −
5 3.14
2. Type Sets
Constraints define a type set, which is the set of types that satisfy the constraint.
A type satisfies a constraint if it implements all the methods in the interface or belongs to the type list.
package main import "fmt" type Numeric interface { int | float64 } func Add[T Numeric](a, b T) T { return a + b } func main() { fmt.Println("Add(4, 6):", Add(4, 6)) fmt.Println("Add(3.5, 5.5):", Add(3.5, 5.5)) }
It will generate the following output −
Add(4, 6): 10 Add(3.5, 5.5): 9
3. Generic Methods
Go does not support generic methods directly, but you can achieve similar functionality using generic types.
package main import "fmt" type Box[T any] struct { Value T } func (b Box[T]) GetValue() T { return b.Value } func main() { box := Box[int]{Value: 42} fmt.Println("box.GetValue():", box.GetValue()) }
It will generate the following output −
box.GetValue(): 42
4. Type Inference
Go can often infer type parameters, so you donât need to specify them. Type inference works for function calls and composite literals.
package main import "fmt" func Print[T any](value T) { fmt.Println(value) } func main() { Print(42) Print("Hello") }
It will generate the following output −
42 Hello
5. Generic Slices and Maps
You can create generic slices and maps using type parameters.
package main import "fmt" func Map[T, U any](slice []T, f func(T) U) []U { result := make([]U, len(slice)) for i, v := range slice { result[i] = f(v) } return result } func main() { nums := []int{2, 4, 6} doubled := Map(nums, func(n int) int { return n * 2 }) fmt.Println("doubled:", doubled) }
It will generate the following output −
doubled: [4 8 12]
6. Generic Channels
You can create generic channels using type parameters. Generic channels are channels in Go that use type parameters to send and receive values of a specific type.
package main import "fmt" func Process[T any](ch <-chan T, f func(T)) { for v := range ch { f(v) } } func main() { ch := make(chan int, 2) ch <- 1 ch <- 2 close(ch) Process(ch, func(v int) { fmt.Println("Processed value:", v) }) }
It will generate the following output −
Processed value: 1 Processed value: 2
7. Embedding Generic Types
You can embed generic types in structs. Embedding generic types means including a generic type within another type, allowing type parameters to be inherited and reused.
package main import "fmt" // Generic Container struct type Container[T any] struct { Value T } // Generic Box struct embedding Container type Box[T any] struct { Container[T] } func main() { box := Box[int]{Container[int]{Value: 64}} fmt.Println("box.Value:", box.Value) }
It will generate the following output −
box.Value: 64
8. Generic Interfaces
You can define generic interfaces. It is interface that use type parameters, allowing them to work with multiple types while maintaining type safety.
package main import "fmt" type Stringer[T any] interface { String() string } // MyType implements Stringer type MyType struct { data string } func (m MyType) String() string { return m.data } func PrintString[T Stringer[T]](value T) { fmt.Println(value.String()) } func main() { val := MyType{data: "Hello, Generics!"} PrintString(val) }
It will generate the following output −
Hello, Generics!
9. Error Handling
Generics can make error handling more complex, especially when working with multiple types.
Use type assertions or custom error types to handle errors in generic code.
package main import ( "fmt" "golang.org/x/exp/constraints" ) // SafeDivide function with error handling func SafeDivide[T constraints.Integer](a, b T) (T, error) { if b == 0 { return 0, fmt.Errorf("division by zero") } return a / b, nil } func main() { result, err := SafeDivide(10, 2) if err != nil { fmt.Println("Error:", err) } else { fmt.Println("SafeDivide(10, 2):", result) } result, err = SafeDivide(10, 0) if err != nil { fmt.Println("Error:", err) } else { fmt.Println("SafeDivide(10, 0):", result) } }
It will generate the following output −
5 Error: division by zero