Irbisa · Шпаргалка

Дженерики и параметры типов

Параметры типов, ограничения, type sets, вывод типов, GC shape stenciling

Middle-разработчик · 19 вопросов

  1. 01

    Что дают дженерики по сравнению с any (interface{}) и когда any всё же уместен?

    Дженерики позволяют одному коду работать с разными типами, а компилятору по-прежнему их проверять: Stack[int] принимает только int, не требует утверждений типа и хранит значения по месту, а []any принимает что угодно и ничего не проверяет до рантайма.

  2. 02

    Как объявить обобщённую функцию и обобщённый тип в Go, например, Map, Filter и Stack[T] с методами?

    Типы-параметры пишутся в квадратных скобках перед обычными параметрами (func Map[T, U any](xs []T, f func(T) U) []U) и после имени типа (type Stack[T any] struct{ items []T }).

    func Map[T, U any](xs []T, f func(T) U) []U {	out := make([]U, 0, len(xs))	for _, x := range xs {		out = append(out, f(x))	}	return out…
  3. 03

    Этот Max не компилируется. Почему и какое ограничение это исправит?

    func Max[T any](a, b T) T {	if a > b {		return a	}	return b}

    any ничего не обещает про T, поэтому в теле можно использовать только операции, которые поддерживает любой тип, а > к ним не относится: у срезов и структур нет порядка.

  4. 04

    Как вернуть нулевое значение типа-параметра T из обобщённой функции и почему не подходят ни nil, ни 0, ни T{}?

    Объявите var zero T и верните её: у типа-параметра нет литерала нулевого значения, ведь nil, 0 и "" подходят только некоторым типам.

    // Find returns the first match. The bool is the answer; the T is only the payload.func Find[T any](xs []T, ok func(T) bool) (T, bool) {	for _, x := range xs {		if ok(x) {			return x, true		}…
  5. 05

    Что означает ~ в ограничении вроде ~string и почему [T string] не принимает type UserID string?

    ~ означает «базовый тип»: ~string допускает любой именованный тип, построенный на string, а голый string допускает только сам string.

    type UserID string // wrong: the type set is exactly {string}; UserID is a different named typefunc FirstBad[T string](xs []T) T { return xs[0] } // right: ~string admits every type whose underlying type is string…
  6. 06

    По чему Go выводит типовые аргументы и почему конструктор вроде NewSet() всё равно приходится писать как NewSet[string]()?

    Вывод опирается на типы переданных аргументов, но никогда на тип, которому присваивается результат.

    type Set[T comparable] struct{ m map[T]struct{} } func NewSet[T comparable]() *Set[T] { return &Set[T]{m: map[T]struct{}{}} } func Map[S, T any](xs []S, f func(S) T) []T {	out := make([]T, 0, len(xs))…
  7. 07

    Почему type Number interface{ ~int | ~float64 } можно использовать только как ограничение, но не как var n Number, и откуда берётся cmp.Ordered?

    Интерфейс, который перечисляет типы (объединение или любой элемент с ~), служит только ограничением, и типом значения он быть не может.

    type Integer interface {	~int | ~int8 | ~int16 | ~int32 | ~int64} type Float interface{ ~float32 | ~float64 }…
  8. 08

    У Cents и Micros есть String(), но v.String() не компилируется для T interface{ Cents | Micros }. Почему и чем может быть элемент объединения?

    У типа-параметра есть только те методы, которые объявляет его ограничение, а объединение объявляет типы, а не методы: общий для обоих членов String невидим.

    type Cents int64type Micros int64 func (c Cents) String() string  { return fmt.Sprintf("%d.%02d", c/100, c%100) }func (m Micros) String() string { return fmt.Sprintf("%d.%06d", m/1e6, m%1e6) }…
  9. 09

    Что выведет этот код и почему в switch нужен any(v), а не v.(type)?

    type MyInt int func Describe[T any](v T) string {	switch any(v).(type) {	case int:		return "int"	case fmt.Stringer:		return "stringer"	default:		return "other"	}} // in main:fmt.Println(Describe(42), Describe(MyInt(42)), Describe(time.Second))

    Выведет int other stringer.

  10. 10

    Найдите баг: OrDefault здесь не подставляет def, и следующая строка падает с паникой.

    // OrDefault returns def when v is unset.func OrDefault[T any](v, def T) T {	if any(v) == nil {		return def	}	return v} var cfg *Config // not loaded yetc := OrDefault(cfg, &Config{Timeout: 5 * time.Second})fmt.Println(c.Timeout)

    any(v) == nil истинно только для nil-интерфейса.

  11. 11

    Index[T comparable](s []T, v T) принимает []any, компилируется, а потом паникует с comparing uncomparable type []int. Почему comparable этого не предотвратил?

    comparable обещает лишь то, что == компилируется.

  12. 12

    Найдите баг: эта типизированная обёртка над sync.Map паникует при первом запросе каждого id пользователя.

    // Cache is a typed wrapper over sync.Map.type Cache[K comparable, V any] struct {	m sync.Map} func (c *Cache[K, V]) Put(k K, v V) { c.m.Store(k, v) } func (c *Cache[K, V]) Get(k K) (V, bool) {	v, ok := c.m.Load(k)	return v.(V), ok} user, ok := users.Get(id)if !ok {	user = loadUser(id)}

    При промахе Load возвращает nil any, и однозначное утверждение типа v.(V) паникует с interface conversion: interface {} is nil, not main.User раньше, чем вызывающий код увидит ok == false.

  13. 13

    Эта сортировка лидерборда выдаёт строки с равным счётом в произвольном порядке. Что не так с функцией сравнения?

    type Row struct {	Name  string	Score int} slices.SortFunc(rows, func(a, b Row) int {	if a.Score < b.Score {		return -1	}	return 1})

    Она никогда не возвращает 0, поэтому каждая из двух строк с одинаковым счётом утверждает, что она больше другой.

  14. 14

    Как написать range-over-func итератор на iter.Seq, что значит false из yield и где делать очистку?

    iter.Seq[V] определён как func(yield func(V) bool): вы вызываете yield для каждого значения и останавливаетесь, как только он вернул false; это значит, что тело цикла сделало break или return.

    // Cleanup belongs to the iterator: it opened the file, so it closes it.func Lines(name string) iter.Seq2[string, error] {	return func(yield func(string, error) bool) {		f, err := os.Open(name)		if err != nil {			yield("", err)…
  15. 15

    Когда тип-параметр оказывается неподходящим инструментом и как выбирать между дженериками, интерфейсом и обычными конкретными типами?

    Тип-параметр нужен, когда код одинаков для любого типа и меняется только тип элемента: контейнеры, алгоритмы в духе slices, операторы вроде +.

  16. 16

    Scale[E ~int32](s []E, c E) []E принимает ваш type Point []int32, но возвращает обычный []int32. Какая сигнатура сохранит Point и почему точка вызова не меняется?

    Параметризуйте и сам срез (func Scale[S ~[]E, E ~int32](s S, c E) S), и типом результата становится то, что передал вызывающий.

    type Point []int32 func (p Point) Norm() int32 {	var n int32	for _, v := range p {		n += v * v…
  17. 17

    До Go 1.27 метод не мог объявлять собственные типы-параметры, как в func (s *Set[T]) Map[U comparable](f func(T) U) *Set[U]. Почему, и что изменилось в Go 1.27?

    Через методы типы реализуют интерфейсы, а обобщённому методу понадобилось бы тело для каждого U, при том что интерфейсы проверяются в рантайме.

    type Set[T comparable] struct{ m map[T]struct{} } func NewSet[T comparable]() *Set[T] { return &Set[T]{m: map[T]struct{}{}} } // fine: the receiver's own parameterfunc (s *Set[T]) Add(v T) { s.m[v] = struct{}{} }…
  18. 18

    Decode[T Validator] не принимает Row, потому что у Validate указательный получатель, а Decode[*Row] делает unmarshal в нулевой указатель. Как ограничить *T?

    Возьмите два типа-параметра ([T any, PT interface{ *T; Validator }]): значение остаётся T, а методы берутся у *T, до которого внутри вы добираетесь через PT(&v).

    type Validator interface{ Validate() error } type Row struct{ N int } // pointer receiver: in *Row's method set, not Row'sfunc (r *Row) Validate() error {…
  19. 19

    Как компилятор Go реализует дженерики (GC shape stenciling со словарями) и когда обобщённая функция медленнее версии на интерфейсах?

    Go компилирует обобщённую функцию один раз на GC shape (грубо говоря, на раскладку памяти базового типа, причём у всех указательных типов она одна) и передаёт каждой инстанциации скрытый словарь с информацией о типах.

    type A struct{ p *int }type B struct{ q *string } func First[T any](xs []T) T { return xs[0] } func Sum[T ~int | ~float64](xs []T) T {…