Generics in Go 1.18

GolangGolangBeginner
Practice Now

This tutorial is from open-source community. Access the source code

Introduction

This lab introduces the concept of generics in Golang. Starting from version 1.18, Golang has added support for generics, which allows us to write more flexible and reusable code.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL go(("`Golang`")) -.-> go/ObjectOrientedProgrammingGroup(["`Object-Oriented Programming`"]) go/ObjectOrientedProgrammingGroup -.-> go/generics("`Generics`") subgraph Lab Skills go/generics -.-> lab-15478{{"`Generics in Go 1.18`"}} end

Generics

The problem to be solved in this lab is to understand how to define and use generic functions and types in Golang.

  • Understand the concept of generics in Golang.
  • Know how to define generic functions with type parameters and constraints.
  • Know how to define generic types with type parameters.
  • Understand how to define methods on generic types.
  • Know how to invoke generic functions with type inference.
$ go run generics.go
keys: [4 1 2]
list: [10 13 23]

There is the full code below:

// Starting with version 1.18, Go has added support for
// _generics_, also known as _type parameters_.

package main

import "fmt"

// As an example of a generic function, `MapKeys` takes
// a map of any type and returns a slice of its keys.
// This function has two type parameters - `K` and `V`;
// `K` has the `comparable` _constraint_, meaning that
// we can compare values of this type with the `==` and
// `!=` operators. This is required for map keys in Go.
// `V` has the `any` constraint, meaning that it's not
// restricted in any way (`any` is an alias for `interface{}`).
func MapKeys[K comparable, V any](m map[K]V) []K {
	r := make([]K, 0, len(m))
	for k := range m {
		r = append(r, k)
	}
	return r
}

// As an example of a generic type, `List` is a
// singly-linked list with values of any type.
type List[T any] struct {
	head, tail *element[T]
}

type element[T any] struct {
	next *element[T]
	val  T
}

// We can define methods on generic types just like we
// do on regular types, but we have to keep the type
// parameters in place. The type is `List[T]`, not `List`.
func (lst *List[T]) Push(v T) {
	if lst.tail == nil {
		lst.head = &element[T]{val: v}
		lst.tail = lst.head
	} else {
		lst.tail.next = &element[T]{val: v}
		lst.tail = lst.tail.next
	}
}

func (lst *List[T]) GetAll() []T {
	var elems []T
	for e := lst.head; e != nil; e = e.next {
		elems = append(elems, e.val)
	}
	return elems
}

func main() {
	var m = map[int]string{1: "2", 2: "4", 4: "8"}

	// When invoking generic functions, we can often rely
	// on _type inference_. Note that we don't have to
	// specify the types for `K` and `V` when
	// calling `MapKeys` - the compiler infers them
	// automatically.
	fmt.Println("keys:", MapKeys(m))

	// ... though we could also specify them explicitly.
	_ = MapKeys[int, string](m)

	lst := List[int]{}
	lst.Push(10)
	lst.Push(13)
	lst.Push(23)
	fmt.Println("list:", lst.GetAll())
}

Summary

Generics are a powerful feature that allows us to write more flexible and reusable code in Golang. With generics, we can define functions and types that work with any type, as long as they satisfy certain constraints. By using generics, we can reduce code duplication and improve code readability and maintainability.

Other Golang Tutorials you may like