Lesson 14 of 18

Generic Functions

Type Parameters

Go 1.18 introduced generics, allowing you to write functions and types that work with any type. Before generics, you had to write separate functions for each type or use interface{} and lose type safety.

Syntax

A generic function declares one or more type parameters in square brackets before the regular parameters:

func Print[T any](val T) {
    fmt.Println(val)
}

T is a type parameter. any is a constraint that means "any type at all". The function can be called with any type:

Print[int](42)
Print[string]("hello")

Type Inference

In most cases the compiler can figure out the type argument from the regular arguments, so you can omit it:

Print(42)       // T inferred as int
Print("hello")  // T inferred as string

"We are the Borg. We will adapt to handle your type." Generics let your functions assimilate int, string, or any type --- resistance is futile.

Constraints

Constraints restrict which types a type parameter can accept. The any constraint allows everything. The comparable constraint allows types that support == and !=:

func Contains[T comparable](slice []T, target T) bool {
    for _, v := range slice {
        if v == target {
            return true
        }
    }
    return false
}

You can also define your own constraint interfaces:

type Number interface {
    int | float64
}

func Sum[T Number](nums []T) T {
    var total T
    for _, n := range nums {
        total += n
    }
    return total
}

Multiple Type Parameters

A function can have more than one type parameter:

func Map[T any, 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
}

Your Task

Write a generic function Filter[T any] that takes a []T and a func(T) bool predicate. It should return a new []T containing only the elements for which the predicate returns true.

Go runtime loading...
Loading...
Click "Run" to execute your code.