В отличие от языков с шаблонами (C++, Rust) или классическими дженериками (Java, C#), Go долгое время обходился без них, но сейчас предлагает несколько подходов для обобщенного программирования:
Наиболее современный и типобезопасный способ:
// Обобщенная функция
func Map[T any, U any](slice []T, mapper func(T) U) []U {
result := make([]U, len(slice))
for i, v := range slice {
result[i] = mapper(v)
}
return result
}
// Обобщенный тип
type Stack[T any] struct {
items []T
}
func (s *Stack[T]) Push(item T) {
s.items = append(s.items, item)
}
Особенности:
Классический Go-way до появления дженериков:
type Sorter interface {
Len() int
Less(i, j int) bool
Swap(i, j int)
}
func Sort(s Sorter) {
// реализация сортировки
}
Плюсы:
Минусы:
Использование go:generate
для создания типоспецифичного кода:
//go:generate genny -in=generic_stack.go -out=int_stack.go -pkg=main gen "ItemType=int"
Популярные инструменты:
genny
(от проекта GoKit)stringer
(из stdlib)mockgen
Наименее типобезопасный, но универсальный способ:
func Print(v interface{}) {
switch val := v.(type) {
case int:
fmt.Println("int:", val)
case string:
fmt.Println("string:", val)
}
}
Когда использовать:
Для сложных случаев динамического поведения:
func GetField(obj interface{}, field string) interface{} {
v := reflect.ValueOf(obj)
return v.FieldByName(field).Interface()
}
Осторожно:
Метод | Типобезопасность | Производительность | Читаемость | Гибкость |
---|---|---|---|---|
Дженерики | ✅ Полная | ⚡ Максимальная | 👍 | Средняя |
Интерфейсы | ❌ Runtime | 🐢 Средняя | 👍 | Низкая |
Генерация кода | ✅ Полная | ⚡ Максимальная | 👎 | Высокая |
interface{} | ❌ Нет | 🐢 Низкая | 👎 | Макс. |
Reflection | ❌ Runtime | 🐌 Очень низкая | 👎 | Макс. |
В современном Go (1.18+) дженерики — основной инструмент для обобщенного программирования. Для legacy-кода или особых случаев можно использовать интерфейсы или code generation. Reflection и interface{} следует применять только когда другие подходы невозможны.