# Go Generics у 2026: Параметри Типів, Обмеження та Питання на Співбесіді > Повний посібник з дженериків Go для підготовки до технічних співбесід. Параметри типів, обмеження, оператор тильди та практичні приклади коду. - Published: 2026-07-08 - Updated: 2026-07-08 - Author: SharpSkill - Tags: go, generics, interview - Reading time: 9 min --- Go 1.18 запровадив дженерики в мову в березні 2022 року, і з того часу ця функціональність розвивалася через версії Go 1.21, 1.22 та наступні. Ця стаття охоплює основні питання співбесіди щодо дженериків Go на 2026 рік, від базових параметрів типів до складних патернів обмежень, про які часто запитують менеджери з найму. > **Що таке Go Generics?** > > Дженерики Go дозволяють писати функції та типи, які працюють з будь-яким типом даних, зберігаючи при цьому безпеку типів під час компіляції. На відміну від інтерфейсів, які використовують перевірку типів під час виконання, дженерики визначають типи під час компіляції, забезпечуючи кращу продуктивність та раннє виявлення помилок. ## Розуміння параметрів типів у Go Параметри типів є основою дженериків Go. Параметр типу — це заповнювач для типу, який вказується при використанні дженерик-функції або типу. ```go // generic.go // Basic generic function with type parameter T func PrintSlice[T any](items []T) { for _, item := range items { fmt.Println(item) } } // Usage - type inference determines T automatically func main() { PrintSlice([]int{1, 2, 3}) // T is int PrintSlice([]string{"a", "b"}) // T is string } ``` Синтаксис квадратних дужок `[T any]` оголошує параметр типу `T`, обмежений `any`. Компілятор Go автоматично виводить конкретний тип з аргументів, усуваючи необхідність явного вказання типу в більшості випадків. ## Поширене питання співбесіди: Яка різниця між `any` та `comparable`? Інтерв'юери часто запитують про вбудовані обмеження `any` та `comparable`. Розуміння їх відмінностей демонструє ґрунтовні знання дженериків. ```go // constraints.go // any: accepts all types (alias for interface{}) func Process[T any](value T) T { return value } // comparable: only types that support == and != func Contains[T comparable](slice []T, target T) bool { for _, v := range slice { if v == target { // This comparison requires comparable return true } } return false } // This compiles Contains([]int{1, 2, 3}, 2) // This fails: slices are not comparable // Contains([][]int{{1}, {2}}, []int{1}) ``` Обмеження `comparable` звужує параметри типів до типів, що підтримують оператори рівності. Зрізи, мапи та функції виключені, оскільки Go не визначає для них рівність на рівні мови. ## Створення власних обмежень типів за допомогою інтерфейсів Go використовує інтерфейси для визначення власних обмежень, розширюючи можливості дженерик-функцій. [Специфікація Go](https://go.dev/ref/spec#Type_constraints) визначає, як працюють елементи типів в інтерфейсах обмежень. ```go // number.go // Custom constraint using type union type Number interface { int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | float32 | float64 } // Generic function constrained to numeric types func Sum[T Number](values []T) T { var total T for _, v := range values { total += v // + operator works because all Number types support it } return total } func main() { fmt.Println(Sum([]int{1, 2, 3})) // 6 fmt.Println(Sum([]float64{1.5, 2.5})) // 4.0 } ``` Об'єднання типів з `|` вказують, які саме типи задовольняють обмеження. Цей підхід забезпечує більший контроль, ніж `any`, уникаючи при цьому перевірок типів під час виконання. ## Питання співбесіди: Поясніть оператор тильди `~` в обмеженнях Оператор тильди `~` в обмеженнях відповідає як типу, так і всім типам, що мають його як базовий тип. Це питання перевіряє розуміння системи типів Go. ```go // underlying.go // ~int matches int and any type with int as underlying type type Signed interface { ~int | ~int8 | ~int16 | ~int32 | ~int64 } // Custom type with int as underlying type type UserID int type Temperature int64 func Abs[T Signed](value T) T { if value < 0 { return -value } return value } func main() { var id UserID = -42 var temp Temperature = -10 fmt.Println(Abs(id)) // 42 - works because ~int matches UserID fmt.Println(Abs(temp)) // 10 - works because ~int64 matches Temperature } ``` Без `~` обмеження `int` відповідало б лише точному типу `int`, а не користувацьким типам як `UserID`. Тильда розширює обмеження, включаючи похідні типи, роблячи дженерик-код більш гнучким. ## Дженерик-типи: структури та методи Дженерик-типи виходять за межі функцій, охоплюючи структури та методи. Цей патерн часто зустрічається в реалізаціях структур даних. ```go // stack.go // Generic Stack type type Stack[T any] struct { items []T } // Push adds an element to the stack func (s *Stack[T]) Push(item T) { s.items = append(s.items, item) } // Pop removes and returns the top element func (s *Stack[T]) Pop() (T, bool) { if len(s.items) == 0 { var zero T // Zero value for type T return zero, false } index := len(s.items) - 1 item := s.items[index] s.items = s.items[:index] return item, true } func main() { intStack := Stack[int]{} intStack.Push(10) intStack.Push(20) val, ok := intStack.Pop() // val=20, ok=true } ``` Методи на дженерик-типах повинні повторювати параметр типу в дужках, але не можуть вводити нові параметри типів. Отримувач `(s *Stack[T])` пов'язує метод з конкретною інстанціацією `Stack`. ## Питання співбесіди: Чому методи не можуть мати власних параметрів типів? Go забороняє додаткові параметри типів у методах, що є дизайнерським рішенням, яке дивує розробників, що приходять з таких мов, як Java або C#. [Пропозиція дженериків Go](https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md) пояснює, що це обмеження існує для збереження керованості системи типів. ```go // This is NOT valid Go code type Container[T any] struct { value T } // ERROR: methods cannot have type parameters // func (c *Container[T]) Transform[U any](fn func(T) U) U { // return fn(c.value) // } // Valid alternative: use a standalone function func Transform[T, U any](c *Container[T], fn func(T) U) U { return fn(c.value) } ``` Обхідним рішенням є використання функцій верхнього рівня з кількома параметрами типів замість методів. Цей дизайн зберігає простоту диспетчеризації методів та уникає складних взаємодій між типами отримувачів та параметрами типів методів. ## Виведення типів та виведення обмежень Виведення типів у Go зменшує багатослівність при виклику дженерик-функцій. Розуміння, коли потрібні явні аргументи типів, допомагає писати чистіший код. ```go // inference.go func Map[T, R any](input []T, transform func(T) R) []R { result := make([]R, len(input)) for i, v := range input { result[i] = transform(v) } return result } func main() { numbers := []int{1, 2, 3} // Type inference: T=int, R=string inferred from arguments strings := Map(numbers, func(n int) string { return fmt.Sprintf("%d", n) }) // Explicit types sometimes required for complex cases // Map[int, string](numbers, converter) } ``` Виведення типів працює від аргументів функції до параметрів типів. Коли компілятор не може вивести типи лише з аргументів, явні аргументи типів у квадратних дужках усувають неоднозначність. ## Пакет constraints: `cmp` та `slices` у стандартній бібліотеці Go 1.21 додав пакет `cmp` з обмеженням `Ordered` та функціями порівняння. Пакет `slices` демонструє ідіоматичний дженерик-код у стандартній бібліотеці. ```go // stdlib.go import ( "cmp" "slices" ) func main() { numbers := []int{3, 1, 4, 1, 5, 9} // slices.Sort uses cmp.Ordered constraint internally slices.Sort(numbers) // [1, 1, 3, 4, 5, 9] // Binary search on sorted slice index, found := slices.BinarySearch(numbers, 4) // cmp.Compare returns -1, 0, or 1 result := cmp.Compare(3, 5) // -1 // cmp.Or returns first non-zero value value := cmp.Or(0, 0, 42, 100) // 42 } ``` [Документація стандартної бібліотеки](https://pkg.go.dev/cmp) показує, як ці пакети використовують дженерики для типобезпечних операцій над впорядкованими типами. Знайомство з цими пакетами демонструє практичні знання дженериків, що виходять за межі теоретичного розуміння. ## Питання співбесіди: Реалізація дженерик-кешу Поширеною вправою на співбесіді є прохання до кандидатів реалізувати дженерик, потокобезпечний кеш. Це тестує дженерики, конкурентність з [пакетом sync](/technologies/go/interview-questions/sync-primitives) та проектування API. ```go // cache.go import ( "sync" "time" ) type Cache[K comparable, V any] struct { mu sync.RWMutex items map[K]cacheItem[V] } type cacheItem[V any] struct { value V expiration time.Time } func NewCache[K comparable, V any]() *Cache[K, V] { return &Cache[K, V]{ items: make(map[K]cacheItem[V]), } } func (c *Cache[K, V]) Set(key K, value V, ttl time.Duration) { c.mu.Lock() defer c.mu.Unlock() c.items[key] = cacheItem[V]{ value: value, expiration: time.Now().Add(ttl), } } func (c *Cache[K, V]) Get(key K) (V, bool) { c.mu.RLock() defer c.mu.RUnlock() item, exists := c.items[key] if !exists || time.Now().After(item.expiration) { var zero V return zero, false } return item.value, true } ``` Ця реалізація використовує `comparable` для ключів (вимога мапи) та `any` для значень. Окрема структура `cacheItem` показує, як дженерик-типи можуть бути вкладеними. Потокобезпека забезпечується `sync.RWMutex`, патерн, що розглядається в [співбесідах щодо конкурентності Go](/technologies/go/interview-questions/concurrency-patterns). ## Нульові значення та обмеження типів Обробка нульових значень у дженерик-коді вимагає розуміння того, як Go ініціалізує змінні параметризованих типів. ```go // zero.go // Return zero value when slice is empty func First[T any](slice []T) T { if len(slice) == 0 { var zero T // Zero value: 0 for int, "" for string, nil for pointers return zero } return slice[0] } // Alternative: return pointer to avoid ambiguity func FirstOrNil[T any](slice []T) *T { if len(slice) == 0 { return nil } return &slice[0] } ``` Патерн `var zero T` створює нульове значення для будь-якого типу `T`. Для типів, де нуль є допустимим значенням (як `0` для цілих чисел), варіант, що повертає вказівник, розрізняє "не знайдено" та "знайдено нуль". ## Просунутий рівень: поєднання кількох обмежень Складні дженерик-функції можуть вимагати, щоб типи задовольняли кілька обмежень. Go обробляє це через вбудовування інтерфейсів. ```go // combined.go // Constraint requiring both ordering and string conversion type Stringable interface { String() string } type OrderedStringable interface { cmp.Ordered Stringable } // Alternative: use type parameters with multiple constraints inline func PrintSorted[T interface{ cmp.Ordered; fmt.Stringer }](items []T) { slices.Sort(items) for _, item := range items { fmt.Println(item.String()) } } ``` Вбудовування інтерфейсів поєднує обмеження, вимагаючи від типів реалізації всіх вбудованих інтерфейсів. Інлайн-синтаксис `interface{ A; B }` забезпечує ту саму функціональність без оголошення іменованого типу обмеження. ## Висновок - Параметри типів з `[T any]` дозволяють писати багаторазовий, типобезпечний код без рефлексії під час виконання - `comparable` обмежує до типів, що підтримують `==` та `!=`, необхідних для ключів мап - Тильда `~` відповідає типам з конкретним базовим типом, розширюючи гнучкість обмежень - Власні обмеження використовують об'єднання типів (`int | string`) для вказання точних дозволених типів - Методи не можуть мати власних параметрів типів; альтернативою є функції верхнього рівня - Пакети стандартної бібліотеки `cmp` та `slices` демонструють ідіоматичні дженерик-патерни - Нульові значення через `var zero T` обробляють порожні або відсутні дані в дженерик-функціях - Потокобезпечні дженерик-структури даних поєднують дженерики з примітивами синхронізації --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/uk/blog/go/go-generics-type-parameters-constraints-interview-questions