Generics Tutorial

Purpose and Scope

This page introduces Go generics from two angles: the user-facing tutorial workflow and the repository surfaces that make the feature real. In Go, generics let a function or type be written once and used with any type in a specified set of types. The official tutorial frames the learning path as starting with duplicate non-generic functions, replacing them with one generic function, then tightening the accepted types with a type constraint. That progression is useful because it keeps the reader focused on code they can run while also revealing the language concepts the compiler and type checkers must understand.

The Go repository is both the implementation of the language and the source for the public distribution. The top-level README describes Go as an open source language for simple, reliable, efficient software, points readers to binary downloads, and identifies the canonical source repository. For a generics learner, that matters because the go command workflow used in tutorials is not separate from this tree: the compiler, assembler, cgo support, testing harnesses, and auxiliary tools are shipped from this source. Sources: README.md

Generics require Go 1.18 or later. The tutorial sequence starts in a new directory, initializes a module with go mod init, and then builds a small program that sums map values of different element types. The important conceptual shift is from writing one function for map[string]int64 and another for map[string]float64 to writing one function with a type parameter list and a constraint. The calling code supplies concrete types, either explicitly or through type inference, and the type checker verifies that the operations used in the function body are valid for every type allowed by the constraint.

Relevant Source Files

  • README.md — establishes the Go repository as the source for the public Go language distribution and points users to official installation and contribution entry points.
  • src/cmd/compile/internal/gc/main.go — shows the compiler entry point that parses flags, reads Go source files, type-checks packages, compiles functions, and writes package output.
  • src/cmd/compile/internal/types2/README.md — describes the internal compiler type checker, its relationship to go/types, and the shared testing model used when changing type-checker behavior.
  • src/go/types/README.md — points public type-checker readers back to the internal types2 organization document, reflecting the close relationship between the two implementations.
  • src/cmd/asm/main.go — shows that assembler processing is a separate toolchain command; generics are resolved before this low-level object generation path.
  • src/cmd/cgo/main.go — represents the C interoperation command and its AST-based package processing, useful for understanding that generics are a Go type-system feature rather than a cgo directive.
  • src/cmd/addr2line/main.go — illustrates a bundled diagnostic tool invoked as go tool addr2line; it operates on compiled binaries rather than participating in generic type checking.
  • misc/go_android_exec/main.go — demonstrates a platform execution wrapper used by the Go tool for Android test execution, showing where generic programs ultimately run like ordinary compiled Go binaries.

Tutorial Flow

Start by creating a new module, because generics examples should be run with the same module-aware workflow used by modern Go code. A minimal session follows the official tutorial shape: create a directory, change into it, and run go mod init example/generics. The module path can be a teaching path for local examples, but production code should choose a path appropriate to its repository. This setup produces a go.mod file and lets go run, go test, and go build operate consistently as the example grows.

mkdir generics
cd generics
go mod init example/generics

Next, write the duplicated version first. The duplication is deliberate: it makes the value of generics obvious before syntax is introduced. One function can sum map[string]int64; another can sum map[string]float64. Both loops have the same shape, but their parameter and result types differ. This is exactly the situation type parameters address: the algorithm is the same, while the concrete type varies within a known set. The tutorial’s lesson is not that every duplicate function should become generic, but that generics are appropriate when shared operations are meaningful across a constrained family of types.

package main
 
import "fmt"
 
func SumInts(m map[string]int64) int64 {
    var s int64
    for _, v := range m {
        s += v
    }
    return s
}
 
func SumFloats(m map[string]float64) float64 {
    var s float64
    for _, v := range m {
        s += v
    }
    return s
}
 
func main() {
    ints := map[string]int64{"first": 34, "second": 12}
    floats := map[string]float64{"first": 35.98, "second": 26.99}
    fmt.Println(SumInts(ints))
    fmt.Println(SumFloats(floats))
}

The generic version introduces a type parameter list after the function name. A type parameter is a name for a type chosen by the caller, and a constraint says which types are valid choices. In the common introductory example, the constraint is a union of numeric types that support +. The function body can then use only operations guaranteed by that constraint. This is why constraints are part of the function signature rather than comments: they are checked by the compiler and become part of the public contract for callers.

func SumNumbers[K comparable, V int64 | float64](m map[K]V) V {
    var s V
    for _, v := range m {
        s += v
    }
    return s
}

When calling a generic function, Go can often infer type arguments from ordinary arguments. A beginner may first write SumNumbers[string, int64](ints) to see the relationship between type parameters and concrete types. Then they can remove the type arguments and call SumNumbers(ints) because the map argument already contains enough information. This inference is part of the reason generic Go code remains readable: most call sites look like normal function calls, while the reusable function definition carries the extra type information.

System-to-Code Mapping

The compiler entry point in src/cmd/compile/internal/gc/main.go is the high-level place to understand where generic source becomes compiled output. Its Main function initializes architecture-specific code generation, parses compiler flags, creates package objects, establishes pseudo-packages for builtins, unsafe, runtime declarations, and map declarations, and then proceeds through front-end processing, type checking, optimization, and object writing. The source comment explicitly describes the command as parsing flags and Go files, type-checking the parsed package, compiling functions to machine code, and writing the compiled package definition. Sources: src/cmd/compile/internal/gc/main.go

The type-checker organization matters because generics are primarily a type-system feature. The internal types2 README explains that there are two closely related type checkers: cmd/compile/internal/types2, used by the compiler, and go/types, the standard-library API. They differ mainly in the syntax tree representation they consume: types2 works with the compiler’s syntax tree, while go/types works with go/ast. The README also says the sources are kept closely in sync, so changes generally need to be made in both places. Sources: src/cmd/compile/internal/types2/README.md, src/go/types/README.md

This split is important for developers reading or testing generic behavior. If you are investigating why a generic function is accepted or rejected during compilation, the compiler path is the internal types2 side. If you are writing an analysis tool, editor integration, or documentation generator that needs to reason about generic code through public APIs, the analogous concepts appear through go/types. The shared testing model described in the README uses annotated Go files with expected error comments, which is how subtle type-checking cases can be preserved as regression tests. Sources: src/cmd/compile/internal/types2/README.md

Toolchain Boundaries

Generics are checked and compiled as part of the ordinary Go compilation pipeline; they are not a separate command. Other command packages in the repository show the boundaries around that pipeline. The assembler entry point initializes architecture support, parses assembly flags, creates object files, and parses Go assembly input. By the time generated or hand-written assembly is involved, generic type parameters are no longer the user-level abstraction being checked. Sources: src/cmd/asm/main.go

The cgo command also lives near the compilation workflow but solves a different problem. Its main package collects package information, parses Go ASTs, records references to C.xxx, tracks exported functions, and manages generated Go and C artifacts. This helps distinguish generic type parameters from cgo names: generics are language-level type abstractions inside Go source, while cgo is an interoperation mechanism that rewrites and augments packages using C declarations. Sources: src/cmd/cgo/main.go

Diagnostic and execution helpers reinforce the same point. addr2line reads addresses and maps them back to function names and source locations for profiling-related workflows; it analyzes compiled binaries rather than deciding whether a generic instantiation is valid. The Android execution wrapper serializes adb commands, copies the Go root, prepares temporary directories, and runs binaries on a device for the Go tool’s test flow. Generic programs participate in these downstream tools like other compiled Go programs after the compiler has accepted and lowered the source. Sources: src/cmd/addr2line/main.go, misc/go_android_exec/main.go

Practical Example and Next Steps

For a first local exercise, keep the example small and verify each step with the standard commands. Use go run . to run the program after adding the non-generic functions, then replace the duplicate functions with a generic SumNumbers function and run it again. If you add a constraint that excludes one of the map value types, the compiler should reject the call before the program runs. That feedback loop is the most direct way to learn how constraints describe permitted operations and how inference chooses concrete types at call sites.

After the introductory example, read the generics concepts material before using type parameters heavily in production APIs. Good generic APIs usually start with a real duplication problem, a small operation set, and a clear constraint. Avoid adding type parameters when ordinary interfaces, concrete types, or simple functions communicate the design better. If you need to understand compiler behavior, continue into the compiler architecture and type-checker documentation; if you need to build user code, continue with modules, tests, and package documentation so generic code remains easy to import, test, and explain.