The limits in go

Зачастую нужно получить наибольшее/наименьшее значение того или иного упорядоченного типа. Для этого в cpp есть заголовочный файл limits, в котором на этапе компиляции вычисляются данные о типе. Хотелось бы чего-то такого и в go. Т.е. понятно, что можно использовать math.MaxInt, но как это использовать с дженериками непонятно (писал об этом ранее).

Недавно нашел способ как это сделать.

Сначала пример использования:

    // Somewhere in code
    
    d := make([]T, n+1)
    for i := range d {
    d[i] = limits.MaxValue[T]()
    }
    d[0] = limits.MinValue[T]()
    
    // ...
    
Пример использования generic limits

Т.е. этот код аналогичен по функционалу такому в cpp.

// ...
#include<limits>
// ...
// Somewhere in cpp code
constexpr int Max = std::numeric_limits<int>::max();
// ...
Аналогичный прием в cpp.

Реализация довольно тривиальна.

package limits

import (
	"reflect"
)

type Limits struct {
	MinValue, MaxValue any
}

var limits = make(map[reflect.Kind]Limits)

func typeOf[T any]() reflect.Type {
	return reflect.TypeOf((*T)(nil)).Elem()
}

func MaxValue[T any]() T {
	return limits[typeOf[T]().Kind()].MaxValue.(T)
}

func MinValue[T any]() T {
	return limits[typeOf[T]().Kind()].MinValue.(T)
}

func AddKindLimits(kind reflect.Kind, l Limits) {
	limits[kind] = l
}

func AddTypeLimits[T any](l Limits) {
	limits[typeOf[T]().Kind()] = l
}

func init() {
	UseOrderedTypes()
}
limits.go

Для этого выделяется словарь limits, в который записывается информация о типе (минимальное/максимальное значения). Используется функция init чтобы на этапе загрузки модуля инициализировать словарь limits.

Пожалуй, самая интересная часть – это функция typeOf. Данная функция не имеет параметров значений, только параметр типа и возвращает мета информацию о типе в виде структуры данных reflect.Type. Таким образом, мы можем использовать рефлексию для дженериков. Более того, подобный шаблон проектирования можно использовать, чтобы хранить другую информацию о типах.

Конечно, тут вычисляется информация о типах не на этапе компиляции, как в cpp, но на этапе инициализации программы, но, думаю, тут это уместно, поскольку, в отличие от cpp, go runtime со сборщиком мусора и не претендует на zero-cost абстракции.

Для расширяемости, чтобы можно было добавлять свои собственные лимиты типов, добавлены функции AddKindLimits, AddTypeLimits.

Функция UseOrderedTypes определена в другом файле и выглядит так:

package limits

import "reflect"

const (
	MaxUInt = ^uint(0)
	MaxInt  = int(MaxUInt >> 1)
	MinInt  = -MaxInt - 1
)

func UseOrderedTypes() {
	AddKindLimits(reflect.Int, Limits{
		MinValue: MinInt,
		MaxValue: MaxInt,
	})
	AddKindLimits(reflect.Uint, Limits{
		MinValue: 0,
		MaxValue: MaxUInt,
	})

	// TODO fill ordered type limits
}

Мы можем определять по аналогии с этой функцией свои подобные UseMyTypes и вызывать их так же в init в тех пакетах, где определены типы.