Обычная функция в Go работает с заранее заданным типом. Универсальная функция использует параметр типа и применяет одну и ту же логику к разным данным. Такой параметр указывается в квадратных скобках после имени функции и затем участвует в сигнатуре.
Функция может обобщаться как по одному типу, так и по нескольким сразу. Когда нужен только один параметр, он объявляется в квадратных скобках и используется в сигнатуре вместо конкретного типа:
func Foo[T any](value T) T
Здесь параметр T
связывает аргумент и возвращаемое значение: тип входа и выхода будет одним и тем же. Если же требуется больше гибкости, можно ввести несколько параметров. Они указываются через запятую в скобках. Например, функция, создающая пару из двух значений разных типов:
func MakePair[K, V any](k K, v V) (K, V)
Параметры типа можно использовать в любых частях сигнатуры: среди аргументов, в возвращаемых значениях, внутри структур и методов. Они играют ту же роль, что и обычные типы, только определяются не заранее, а в момент вызова функции. Если функция принимает срез []T
, то она обязана вернуть результат того же типа T
, что был подставлен при вызове. Это гарантирует согласованность и строгую проверку типов.
Пример функции поиска минимального элемента показывает, как параметр типа можно ограничить операциями сравнения. Для этого используют constraints.Ordered
:
import "golang.org/x/exp/constraints"
func Min[T constraints.Ordered](a, b T) T {
if a < b {
return a
}
return b
}
Здесь T
подходит для чисел и строк. Вызов Min(3, 7)
вернёт 3
, а Min("go", "java")
— строку "go"
.
Другой пример — поиск значения в срезе. Здесь параметр T
участвует и в типе элементов, и в типе искомого значения:
func Contains[T comparable](slice []T, target T) bool {
for _, v := range slice {
if v == target {
return true
}
}
return false
}
Такое описание не позволит искать строку в срезе чисел — строгая типизация сохраняется.
Функция реверса показывает, что параметр типа может использоваться только для элементов, а логика при этом остаётся общей:
func Reverse[T any](slice []T) []T {
n := len(slice)
result := make([]T, n)
for i, v := range slice {
result[n-1-i] = v
}
return result
}
Она одинаково работает для []int
, []string
и любых других срезов.
Параметры типа делают возможным написание лаконичных и универсальных функций. Они позволяют описывать общую логику один раз и применять её к разным данным, сохраняя строгую проверку на этапе компиляции.
Самостоятельная работа
Потренируемся в написании простой универсальной функции без ограничений.
Реализуйте фнукцию
Repeat[T any](v T, n int) []T
, которая создаёт срез изn
копий значенияv
.Проверьте на нескольких типах (например,
int
иstring
).
Показать решение
package main
import "fmt"
func Repeat[T any](v T, n int) []T {
if n <= 0 {
return []T{}
}
xs := make([]T, n)
for i := 0; i < n; i++ {
xs[i] = v
}
return xs
}
func main() {
fmt.Println(Repeat(7, 3)) // [7 7 7]
fmt.Println(Repeat("go", 2)) // [go go]
}
Дополнительные материалы
- Спецификация — Объявления функций
- Блог Go — Введение в дженерики
- Документация — Руководство по дженерикам
- Спецификация — Параметры типа (Type parameters)
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.