Golang пощупаем дженерики

Скоро выйдет релиз 1.18 в котором появятся долгожданные дженерики. Они позволят сделать универсальные методы. Я написал пару примеров для себя. Может быть они будут интересны кому-нибудь ещё.

Интерфейсы any, comparable, constraints. и ~

Появились новые ключевые слова

any — аналог interface{}. Это ключевое слово можно использовать в любом месте. Например при определении типа переменной или при опрредении типа поля в структуре. Вот такой код ошибок не вызовет:

func TestDo(v any) any { var k any k = 10 return k
} type Test interface { Some() any
}

comparable — это интерфейс который определяет типы которые могут бысь сравнены с помощью == и !=. Переменные такого типа создать нельзя. (var j comparable будет вызывать ошибку.)

Появилась возможность определять интерфейсы, которые можно будет использовать в параметризованных функциях и типах. Переменные такого типа создать нельзя. (var j Int будет вызывать ошибку.)

type Int interface { int | int32 | int64
}

Под данный интерфейс подходят только описанные в нём тиипы.

Если добавить знак ~ перед типами то интерфейсу будут соотвествовать и производные типы, например myInt из примера ниже:

type Int interface { ~int | ~int32 | ~int64
}
type myInt int

Разработчики golang создали для нас уже готовый набор интерфейсов, котрый очень удобно использовать: https://pkg.go.dev/golang.org/x/exp/constraints

Параметризованные функции

Давайте рассмотрим пример функции Max. Эта функция возвращает максимум из двух переданных значений. Причём тип может быть любым.

import "constraints"
func Max[T constraints.Ordered](a T, b T) T { if a > b { return a } return b
}

Ограничения на используемые типы описываются в квадратных скобочках. В качестве огранияения для типов можно использовать любой интерфейс и особые интерфейсы описанные выше.

Давайте проведём эксперимент и посмотрим какой тип будет у параметра параметризованной функции:

package main import ( "fmt" "reflect"
) func TypeTest[T any](a T) { fmt.Println(reflect.TypeOf(a))
} func main() { TypeTest("abc") TypeTest(1.0) TypeTest(1)
}

Результат соответствует ожиданиям:

string
float64
int

Для слайсов и мапов был создан набор готовых полезных функций:

https://pkg.go.dev/golang.org/x/exp/slices

https://pkg.go.dev/golang.org/x/exp/maps

Параметризованные типы

Давайте сделаем пример мапы и добавим пару методов. Один из которых вытащит все ключи, а второй напишет в консоль тип.

package main import ( "fmt" "reflect"
) type myMap[K comparable, V any] map[K]V func (m myMap[K, V]) Keys() []K { res := make([]K, 0, len(m)) for k := range m { res = append(res, k) } return res
}
func (m myMap[K, V]) Type() { fmt.Println(reflect.TypeOf(m))
} func main() { mp := myMap[int, string]{5: "sd"} fmt.Println(mp.Keys()) mp.Type()
}

Вывод:

[5]
main.myMap[int,string]

Немного моих мыслей

Сейчас появятся библиотеки которые будут реализовывать функции Max, Min, IIF и подобные. Вот мой пример

А так же появятся библиотеки реализующие Where, Order, Any и подобные методы для мапов и слайсов.

Я думаю что дженерики стоит использовать только в самых общих библиотеках. Их использование в логике будет создавать хаос. Но найдутся те, кто со мной не согласен.

Читайте так же: