Generics
Generics
Introduction
In this lesson, you'll learn about generics in Go. Coming from C#, you already have a foundation for understanding this concept. We'll build on that knowledge while highlighting the key differences.
In C#, you're familiar with generics.
Go has its own approach to generics, which we'll explore step by step.
The Go Way
Let's see how Go handles this concept. Here's a typical example:
package main
import "cmp"
// Go generics (Go 1.18+) — type constraints via interfaces
func Max[T cmp.Ordered](a, b T) T {
if a > b {
return a
}
return b
}
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 {
n := len(s.items) - 1
item := s.items[n]
s.items = s.items[:n]
return item
}Comparing to C#
Here's how you might have written similar code in C#:
// C# generics — rich constraints
T Max<T>(T a, T b) where T : IComparable<T> {
return a.CompareTo(b) > 0 ? a : b;
}
class Stack<T> {
private List<T> _items = new();
public void Push(T item) => _items.Add(item);
public T Pop() => _items[^1]; // simplified
}You may be used to different syntax or behavior.
Go generics use [T constraint] syntax — square brackets, not angle brackets
You may be used to different syntax or behavior.
Constraints are interfaces — cmp.Ordered, any, comparable are built-in
You may be used to different syntax or behavior.
Go lacks some C# constraints: no new(), no struct/class distinction
You may be used to different syntax or behavior.
C# generics are more mature with more constraint types
You may be used to different syntax or behavior.
Go's any = interface{}, comparable = types usable as map keys
Step-by-Step Breakdown
1. Generic Function Syntax
Go uses [TypeParam Constraint] before the parameter list. The constraint is an interface that defines what operations are available.
T Identity<T>(T value) => value;
T Max<T>(T a, T b) where T : IComparable<T>
=> a.CompareTo(b) > 0 ? a : b;func Identity[T any](value T) T {
return value
}
// cmp.Ordered = any type supporting < > <= >=
func Max[T cmp.Ordered](a, b T) T {
if a > b { return a }
return b
}2. Generic Types
Generic structs use the same [T constraint] syntax. When calling methods on a generic type, Go infers the type argument.
class Pair<T, U> {
public T First { get; set; }
public U Second { get; set; }
}
var p = new Pair<string, int> { First = "x", Second = 1 };type Pair[T, U any] struct {
First T
Second U
}
p := Pair[string, int]{First: "x", Second: 1}
// Type inference in function calls
func MakePair[T, U any](f T, s U) Pair[T, U] {
return Pair[T, U]{First: f, Second: s}
}
p2 := MakePair("x", 1) // inferred3. Custom Constraints
Go constraints are interfaces. Use the ~ operator for underlying-type matching — useful for type aliases.
// C# — multiple constraints
void Process<T>(T item)
where T : IEntity, IComparable<T>, new() { }// Go — constraint is just an interface
type Number interface {
~int | ~int64 | ~float64 // union of types
}
func Sum[T Number](nums []T) T {
var total T
for _, n := range nums {
total += n
}
return total
}4. Generics vs Interface{}
Before Go 1.18, developers used interface{} for generic-like code with runtime type assertions. Generics provide compile-time safety instead.
// Old Go (pre-1.18) — like C# object, loses type info
func OldMax(a, b interface{}) interface{} {
// type assertion needed — not type safe
}// Modern Go (1.18+) — type safe generics
func Max[T cmp.Ordered](a, b T) T {
if a > b { return a }
return b
}
// No type assertions needed:
n := Max(3, 5) // int
f := Max(3.1, 2.9) // float64Common Mistakes
When coming from C#, developers often make these mistakes:
- Go generics use [T constraint] syntax — square brackets, not angle brackets
- Constraints are interfaces — cmp.Ordered, any, comparable are built-in
- Go lacks some C# constraints: no new(), no struct/class distinction
Key Takeaways
- Go generics use [T Constraint] square brackets, not angle brackets
- Constraints are interfaces: any, comparable, cmp.Ordered, or custom
- ~ in constraints matches underlying types (for type aliases)
- Generics replaced the unsafe interface{}/any pattern from pre-1.18 code