Go > Reflection and Generics > Generics (Go 1.18+) > Generic types and constraints

Generic Min Function with Custom Comparable Constraint

This example demonstrates how to create a custom type constraint for comparable types and use it in a generic `Min` function to find the minimum value between two inputs of the same type.

Introduction to Custom Type Constraints

Go allows you to define your own custom type constraints using interfaces. This provides flexibility in defining specific requirements for the types used in your generic functions. This example shows how to create a custom constraint for comparable types.

Defining a Custom Comparable Type Constraint

This code defines a custom type constraint called `Comparable`. It uses the `~` operator to specify that the underlying type must be one of the listed types (integers, floats, or strings). The `~` operator indicates that the type constraint is satisfied if the underlying type is one of the listed types, even if it's a named type based on those types.

package main

import "fmt"

type Comparable interface {
	~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~float32 | ~float64 | ~string
}

Implementing a Generic Min Function

This code defines a generic function called `Min`. It takes two arguments `a` and `b` of type `T` (where `T` must satisfy the `Comparable` constraint) as input and returns the minimum of the two values. Because `T` is constrained to `Comparable`, the `<` operator can be safely used.

func Min[T Comparable](a, b T) T {
	if a < b {
		return a
	}
	return b
}

Example Usage

This code shows how to use the `Min` function with integers, floats, and strings. It creates variables of each type and calls the `Min` function with the variables, then prints the minimum value for each type.

func main() {
	int1 := 10
	int2 := 20
	minInt := Min(int1, int2)

	float1 := 3.14
	float2 := 2.71
	minFloat := Min(float1, float2)

	string1 := "apple"
	string2 := "banana"
	minString := Min(string1, string2)

	fmt.Printf("Minimum Integer: %v\n", minInt)
	fmt.Printf("Minimum Float: %v\n", minFloat)
	fmt.Printf("Minimum String: %v\n", minString)
}

Complete Code Example

This is the complete code example, including the package declaration, custom type constraint definition, generic function definition, and example usage in the `main` function.

package main

import "fmt"

type Comparable interface {
	~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~float32 | ~float64 | ~string
}

func Min[T Comparable](a, b T) T {
	if a < b {
		return a
	}
	return b
}

func main() {
	int1 := 10
	int2 := 20
	minInt := Min(int1, int2)

	float1 := 3.14
	float2 := 2.71
	minFloat := Min(float1, float2)

	string1 := "apple"
	string2 := "banana"
	minString := Min(string1, string2)

	fmt.Printf("Minimum Integer: %v\n", minInt)
	fmt.Printf("Minimum Float: %v\n", minFloat)
	fmt.Printf("Minimum String: %v\n", minString)
}

Concepts Behind the Snippet

The core concepts behind this snippet are custom type constraints and generics. By defining a custom type constraint, you can restrict the types that can be used in your generic functions to types that support specific operations, such as comparison.

Real-Life Use Case

A real-life use case is when you need to write a generic sorting algorithm. The sorting algorithm needs to compare elements, so the type constraint would ensure that the types being sorted are comparable.

Best Practices

When defining custom type constraints, be as specific as possible. This ensures type safety and helps to prevent unexpected behavior. Use the `~` operator when you want to allow named types based on the underlying types.

Interview Tip

When discussing custom type constraints in interviews, be prepared to explain how they work, why they are useful, and provide examples of when you would use them. Be ready to discuss the trade-offs between custom constraints and predefined constraints.

When to Use Them

Use custom type constraints when you need to restrict the types that can be used in your generic functions to types that support specific operations or have specific properties.

Alternatives

One alternative could be using type switches and interface{}, which is not typesafe and performant. It's always better to use generics instead

Memory Footprint

As mentioned in the previous example, generics can lead to code bloat but go compiler is smart enough to minimize this effect

Pros

  • Type safety
  • Flexibility in defining type requirements
  • Code reuse

Cons

  • Can increase compilation time
  • Requires Go 1.18 or later

FAQ

  • What does the `~` operator do in a type constraint?

    The `~` operator indicates that the type constraint is satisfied if the underlying type is one of the listed types, even if it's a named type based on those types.
  • Can I use multiple type constraints in a generic function?

    Yes, you can use multiple type constraints by combining them in an interface or using multiple type parameters with separate constraints.
  • How do custom constraints improve code maintainability?

    By restricting the types allowed in a generic function, you reduce the risk of unexpected behavior and make the code easier to understand and maintain.