вступление

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

Первая бета-версия go1.18 была выпущена несколько дней назад, и с тех пор я попробовал ее. И это работает так же, как и ожидалось, и это быстро! Это избавит меня от большого количества копий даже в рамках одного проекта. (отметьте здесь можно найти мою попытку дженериков карт Read-Copy-Update )

Проблема

Но вернемся к проблеме, которая раздражала меня с тех пор, как я начал использовать golang. Представим, что у нас есть следующий интерфейс:

type Logger interface {
    WithField(name, value string) Logger
    Info(message string)
}

Это очень простой регистратор, в который вы можете добавлять поля, и он возвращает сам себя. Нам действительно не нужны дженерики для этого. Но без них нам нужно знать о самом интерфейсе, который эффективно ломает неявное взаимодействие. Как видите, нам нужно вернуть Logger из WithField, чтобы реализовать интерфейс, который тесно связывает наш пакет с пакетом, определившим интерфейс:

type MyLogger struct {
}
func (m *MyLogger) WithField(string, string) Logger {
 ...
}
func (m *MyLogger) Info(string) {
}

Решение

При попытке реализовать это с помощью дженериков первой прямой идеей было бы реализовать что-то вроде этого:

type GenericLogger[T any] interface {
    WithField(string, string) T
    Info(string)
}

Однако, если присмотреться, это само по себе еще не совсем то, что нам нужно. Мы возвращаем T, который может быть любым, но на самом деле это не GenericLogger! Нам также не разрешено ссылаться здесь на собственный интерфейс.

Нам нужно убедиться, что T действительно тот тип, который мы хотим реализовать. Для этого достаточно интерфейса, но есть кое-что, что нам нужно сделать при его использовании:

func DoStuff[T GenericLogger[T]](t T) {
    t.WithField("go", "1.18").Info("is awesome")
}

К чтению этого, безусловно, вам нужно сначала привыкнуть, но здорово, что это работает. В основном происходит то, что мы принимаем тип T, который должен реализовать GenericLogger[T]. Итак, на этом этапе мы в основном рекурсивно сообщаем GenericLogger, что он сам является общим объектом, который должен быть возвращен.

Попробуй на детской площадке!

Заключение

Раньше Go уже был мощным, но я верю, что благодаря дженерикам возникнет новая искра интереса. Становясь менее подробным, мы сможем больше сосредоточиться на написании бизнес-логики, и это поможет использовать его в еще большем количестве областей.

Структурная типизация, как одна из основных сильных сторон го (ИМХО), также будет более мощной.

Я с нетерпением жду релиза, чтобы использовать его в производстве!

Примечания:

Спасибо Кристофу Бергеру, который указал мне, как мы можем использовать дженерики таким образом!