DEV Community

Cover image for Everything about Generics in Go
Leapcell
Leapcell

Posted on

6 3 2 4 1

Everything about Generics in Go

Cover

What is Generics

Generic programming is a style or paradigm of programming languages. Generics allow programmers to write code in strongly-typed programming languages using some types that are specified later, and these types are provided as parameters during instantiation.

Generics enable you to write code that can be applied to multiple types without repeating the same logic for each type. This improves code reusability, flexibility, and type safety.

In Go, generics are implemented through type parameters. A type parameter is a special kind of parameter used as a placeholder that can be any type. They are used in the definitions of functions, methods, and types, and are replaced with specific types during concrete calls.

Before Generics

Consider this requirement: implement a function that takes two int parameters and returns the smaller of the two. This is a very simple requirement, and we can easily write the following code:

func Min(a, b int) int {
    if a < b {
        return a
    }
    return b
}
Enter fullscreen mode Exit fullscreen mode

This looks great, but the function has a limitation: the parameters can only be of type int. If the requirement expands and we need to support comparison of two float64 values and return the smaller one, we might write:

func Min(a, b int) int {
    if a < b {
        return a
    }
    return b
}
func MinFloat64(a, b float64) float64 {
    if a < b {
        return a
    }
    return b
}
Enter fullscreen mode Exit fullscreen mode

You may notice that as soon as the requirement expands, we have to make changes as well, constantly doing repetitive work. Generics are exactly what solve this problem.

import "golang.org/x/exp/constraints"

func Min[T constraints.Ordered](x, y T) T {
    if x < y {
        return x
    }
    return y
}
Enter fullscreen mode Exit fullscreen mode

Basic Syntax of Generics

// Function definition
func F[T any](p T){...}
// Type definition
type M[T any] []T

// Constraint represents specific type constraints, such as any, comparable
func F[T Constraint](p T){..}

// The “~” symbol is used to represent underlying type constraints
type E interface {
   ~string
}

// Specify several types
type UnionElem interface {
   int | int8 | int32 | int64
}
Enter fullscreen mode Exit fullscreen mode

The ~ Symbol

In Go generics, the ~ symbol is used to represent an underlying type constraint.

For example, ~int means accepting any type whose underlying type is int, including custom types. If there is a custom type MyInt whose underlying type is int, then this constraint can accept the MyInt type.

type MyInt int

type Ints[T int | int32] []T

func main() {
   a := Ints[int]{1, 2}     // Correct
   b := Ints[MyInt]{1, 2}   // Compilation error
   println(a)
   println(b)
}
Enter fullscreen mode Exit fullscreen mode

MyInt does not satisfy int | int32 (possibly missing ~ for int in int | int32)compilerInvalidTypeArg

Just modify it as follows:

type Ints[T ~int | ~int32] []T
Enter fullscreen mode Exit fullscreen mode

Type Constraints

  • any: Accepts any type
  • comparable: Supports == and != operations
  • ordered: Supports comparison operators like >, <

For other types, refer to: https://pkg.go.dev/golang.org/x/exp/constraints

When to Use Generics

When to Use Generics

  • For operations on language-defined container types: When writing functions that operate on special container types defined by the language, such as slices, maps, and channels, and the function code does not make specific assumptions about the element type, using type parameters may be useful. For example, a function that returns a slice of all keys from any type of map.
  • General-purpose data structures: For general-purpose data structures like linked lists or binary trees, using type parameters can produce more generic data structures, or store data more efficiently, avoid type assertions, and perform complete type checking at build time.
  • Implementing generic methods: When different types need to implement some common methods, and these implementations look exactly the same, using type parameters may be useful. For example, a generic type implementing sort.Interface for any slice type.
  • Prefer functions over methods: When you need something like a comparison function, prefer using functions instead of methods. For general-purpose data types, it is preferable to use functions rather than writing constraints that require methods.
// SliceFn implements sort.Interface for a slice of T.
type SliceFn[T any] struct {
    s    []T
    less func(T, T) bool
}

func (s SliceFn[T]) Len() int {
    return len(s.s)
}
func (s SliceFn[T]) Swap(i, j int) {
    s.s[i], s.s[j] = s.s[j], s.s[i]
}
func (s SliceFn[T]) Less(i, j int) bool {
    return s.less(s.s[i], s.s[j])
}
Enter fullscreen mode Exit fullscreen mode
// SortFn sorts s in place using a comparison function.
func SortFn[T any](s []T, less func(T, T) bool) {
    sort.Sort(SliceFn[T]{s, less})
}
Enter fullscreen mode Exit fullscreen mode

When Not to Use Generics

  • Do not replace interface types: If you only need to call methods on a value of a certain type, you should use interface types rather than type parameters. For example, you should not change a function that uses interface types to one that uses type parameters.
  • Do not use type parameters when method implementations differ: If the method implementation is different for each type, you should use interface types and write different method implementations, rather than using type parameters.
  • Use reflection appropriately: If some operations must support types that do not even have methods, and the operations are different for each type, then use reflection. For example, the encoding/json package uses reflection.

Simple Guideline

If you find yourself writing exactly the same code multiple times, with the only difference being the types used, you can consider using type parameters. In other words, you should avoid using type parameters until you notice that you are about to write exactly the same code multiple times.

Trivia

Why use square brackets [] instead of the angle brackets < > that are common in other languages?

https://github.com/golang/proposal/blob/master/design/15292/2013-12-type-params.md

Use angle brackets, as in Vector. This has the advantage of being familiar to C++ and Java programmers. Unfortunately, it means that f(true) can be parsed as either a call to function f or a comparison of f<T (an expression that tests whether f is less than T) with (true). While it may be possible to construct complex resolution rules, the Go syntax avoids that sort of ambiguity for good reason.


We are Leapcell, your top choice for hosting Go projects.

Leapcell

Leapcell is the Next-Gen Serverless Platform for Web Hosting, Async Tasks, and Redis:

Multi-Language Support

  • Develop with Node.js, Python, Go, or Rust.

Deploy unlimited projects for free

  • pay only for usage — no requests, no charges.

Unbeatable Cost Efficiency

  • Pay-as-you-go with no idle charges.
  • Example: $25 supports 6.94M requests at a 60ms average response time.

Streamlined Developer Experience

  • Intuitive UI for effortless setup.
  • Fully automated CI/CD pipelines and GitOps integration.
  • Real-time metrics and logging for actionable insights.

Effortless Scalability and High Performance

  • Auto-scaling to handle high concurrency with ease.
  • Zero operational overhead — just focus on building.

Explore more in the Documentation!

Try Leapcell

Follow us on X: @LeapcellHQ


Read on our blog

Gen AI apps are built with MongoDB Atlas

Gen AI apps are built with MongoDB Atlas

MongoDB Atlas is the developer-friendly database for building, scaling, and running gen AI & LLM apps—no separate vector DB needed. Enjoy native vector search, 115+ regions, and flexible document modeling. Build AI faster, all in one place.

Start Free

Top comments (4)

Collapse
 
kurealnum profile image
Oscar

Go's syntax is still so weird to me. Great article though!

Collapse
 
ryansgi profile image
Ryan B

Best stay far away from RUST then my friend. :D

Collapse
 
kurealnum profile image
Oscar

I really like Rust's syntax actually! I don't know what it is about Go's syntax, but it just doesn't sit well with me.

Thread Thread
 
ryansgi profile image
Ryan B

That's so strange to me! Awesome, but strange.

Dev Diairies image

User Feedback & The Pivot That Saved The Project ↪️

We’re following the journey of a dev team building on the Stellar Network as they go from hackathon idea to funded startup, testing their product in the real world and adapting as they go.

Watch full video 🎥