Skip to content

Generics

Generics enables you to write functions and data structures that work with different types while maintaining type safety. You can define functions and structures that operate on various types without sacrificing the compiler's ability to catch type errors at compile time.

Example

run command
go run src/generics/basic.go
package main

import (
    "fmt"
)

func FindMax[T int | float64](data []T) T {
    if len(data) == 0 {
        return 0
    }

    max := data[0]
    for _, value := range data {
        if value > max {
            max = value
        }
    }

    return max
}

func main() {
    ints := []int{3, 7, 1, 9, 4, 6}
    floats := []float64{3.14, 2.71, 1.618, 2.718}

    fmt.Println("FindMax integer:", FindMax(ints))
    fmt.Println("FindMax float:", FindMax(floats))
}
output
FindMax integer: 9
FindMax float: 3.14

Combine Multiple Types (Constraint)

In Go, generic constraints are a way to specify requirements for the type parameters used in generic functions or data structures. Constraints ensure that the generic code can only be used with types that meet certain criteria.

Example

run command
go run src/generics/constraint.go
package main

import (
    "fmt"
)

type ValidNumbers interface {
    int | float64
}

func FindMax[T ValidNumbers](data []T) T {
    if len(data) == 0 {
        return 0
    }

    max := data[0]
    for _, value := range data {
        if value > max {
            max = value
        }
    }

    return max
}

func main() {
    ints := []int{3, 7, 1, 9, 4, 6}
    floats := []float64{3.14, 2.71, 1.618, 2.718}

    fmt.Println("FindMax integer:", FindMax(ints))
    fmt.Println("FindMax float:", FindMax(floats))
}
output
FindMax integer: 9
FindMax float: 3.14

Tilde(~) - Closer Than Identical Types

The special tilde (~) symbol, often referred to as the approximation constraint, is used in Go generics to indicate that a type should approximately match one of the specified types or their derived types. Without the tilde, the function or method constrained by generic types would only accept exact types declared in the constraints and not their derived types. The tilde allows for a broader match, including derived types, making your code more flexible and accommodating related types.

Example

run command
go run src/generics/approximation_constraint.go
package main

import (
    "fmt"
)

type NumberInt int
type NumberFloat float64

type ValidNumbers interface {
    ~int | ~float64
}

func FindMax[T ValidNumbers](data []T) T {
    if len(data) == 0 {
        return 0
    }

    max := data[0]
    for _, value := range data {
        if value > max {
            max = value
        }
    }

    return max
}

func main() {
    ints := []NumberInt{3, 7, 1, 9, 4, 6}
    floats := []NumberFloat{3.14, 2.71, 1.618, 2.718}

    fmt.Println("FindMax integer:", FindMax(ints))
    fmt.Println("FindMax float:", FindMax(floats))
}
output
FindMax integer: 9
FindMax float: 3.14

Comparable

In Go generics, you can use the comparable constraint to specify that a type should be comparable. This constraint ensures that the type can be used with comparison operators like == and !=.

Example

run command
go run src/generics/comparable.go
package main

import "fmt"

func CheckNumbers[T comparable](a T, b T) bool {
    if a == b {
        return true
    }
    return false
}

func FindIndex[T comparable](data []T, value T) int {
    for i, data_value := range data {
        if data_value == value {
            return i
        }
    }
    return -1
}

func main() {
    fmt.Println("CheckNumbers: ", CheckNumbers(10, 10.5))
    fmt.Println("CheckNumbers: ", CheckNumbers(10.5, 10.5))

    ints := []int{3, 7, 1, 9, 4, 6}
    fmt.Println("FindMax integer:", FindIndex(ints, 300))
    fmt.Println("FindMax integer:", FindIndex(ints, 9))
    fmt.Println("FindMax integer:", FindIndex(ints, 10))
}
output
CheckNumbers:  false
CheckNumbers:  true
FindMax integer: -1
FindMax integer: 3
FindMax integer: -1

References