Go > Reflection and Generics > Generics (Go 1.18+) > Defining generic functions
Defining Generic Functions in Go
This snippet demonstrates how to define and use generic functions in Go (Go 1.18+). Generics allow you to write functions that can operate on different types without the need for type assertions or code duplication. This example covers basic generic function declaration and usage with type constraints.
Basic Generic Function Definition
This code defines a generic function `Max` that finds the maximum of two values of the same type. The `[T constraints.Ordered]` part declares a type parameter `T`. The `constraints.Ordered` interface (from the `golang.org/x/exp/constraints` package) ensures that the type `T` supports the `>` operator (i.e., it's comparable). The `main` function demonstrates how to call the `Max` function with different types (int, float64, string). Go's type inference mechanism automatically deduces the type `T` based on the arguments passed to the function, but you can also explicitly specify the type. You can install the necessary package using: `go get golang.org/x/exp/constraints`.
// Generic function to find the maximum of two values.
// Requires the type parameter T to implement the constraints.Ordered interface.
func Max[T constraints.Ordered](a, b T) T {
if a > b {
return a
}
return b
}
func main() {
intMax := Max(10, 5) // Type inference: T is int
floatMax := Max(3.14, 2.71) // Type inference: T is float64
stringMax := Max("banana", "apple") // Type inference: T is string
println("Max int:", intMax)
println("Max float:", floatMax)
println("Max string:", stringMax)
}
Concepts Behind Generics
Generics introduce the concept of *type parameters* to Go. A type parameter is a placeholder for a specific type that will be determined when the generic function is called. *Type constraints* specify the allowed types for a type parameter. In the example, `constraints.Ordered` is a type constraint that ensures the type parameter `T` can be compared using operators like '>'. This improves type safety and allows writing more reusable code.
Real-Life Use Case
Generic functions are valuable in scenarios where you need to perform similar operations on different data types. For example, a generic sorting function could sort slices of integers, floats, strings, or custom structs without requiring separate implementations for each type. Another common use case is in data structures like generic stacks, queues, or trees, where the data type stored is parameterized.
Best Practices
Interview Tip
Be prepared to explain the benefits of generics (code reuse, type safety) and the concepts of type parameters and type constraints. Practice writing simple generic functions and data structures to demonstrate your understanding. Be ready to discuss when and why you would choose to use generics over other approaches like interfaces or type assertions.
When to Use Them
Use generic functions when you have code that performs the same logic regardless of the underlying data type, and you want to avoid code duplication. Generics are particularly useful for implementing data structures and algorithms that operate on collections of data. If the logic varies significantly depending on the data type, it might be better to use interfaces or type assertions.
Memory Footprint
The memory footprint of generic functions is generally similar to that of non-generic functions. The compiler creates specialized versions of the generic function for each type used, so there's no runtime overhead associated with type assertions or interface dispatch. The space required will depend on the size of the types involved. Since specialized versions are created for each type, using too many different types with a generic function can increase the overall code size.
Alternatives
Pros
Cons
Generic Function with Multiple Type Parameters
This code presents an example of a generic function that attempts to swap two variables of different types. It introduces two type parameters, `T` and `U`, both constrained by `any` (meaning any type is allowed). Note: This 'swap' implementation using `any` and type assertions is generally not recommended due to its complexity and potential runtime errors. It's included for illustration purposes only. A true generic swap function usually requires both variables to be of the same type.
// Generic function that swaps the values of two variables of different types.
func Swap[T, U any](a *T, b *U) {
temp := *a
*a = T(any(*b).(U))
*b = U(any(temp).(T))
}
// Example usage (uncomment to run, but note it has issues)
/*
func main() {
intVal := 10
stringVal := "hello"
Swap(&intVal, &stringVal)
fmt.Println("intVal:", intVal)
fmt.Println("stringVal:", stringVal)
}
*/
FAQ
-
What is a type parameter?
A type parameter is a placeholder for a specific type that will be determined when the generic function is called. It's declared within square brackets after the function name (e.g., `[T any]`). -
What is a type constraint?
A type constraint specifies the allowed types for a type parameter. It ensures that the generic function can only be used with types that satisfy the constraint (e.g., `constraints.Ordered`). -
How does Go infer the type of a type parameter?
Go's type inference mechanism automatically deduces the type of a type parameter based on the arguments passed to the generic function. If the type cannot be inferred, you must explicitly specify it when calling the function. -
Can I define multiple type parameters in a generic function?
Yes, you can define multiple type parameters, as shown in the `Swap` example (although that example has issues and isn't a practical implementation). -
How can I constrain a type parameter to be a specific type?
You can use the `comparable` constraint to ensure that the type parameter can be compared using `==` and `!=`. For other types, you can create your own interface constraints.