Le generics sono state introdotte in Go a partire dalla versione 1.18. Per molto tempo Go è stato considerato un linguaggio conservativo, dove per semplicità erano escluse le generics, ma con l'aumento del numero di progetti e lo sviluppo dell'ecosistema è emersa la necessità di scrivere funzioni e strutture universali. Questo è particolarmente critico per le strutture contenitore, gli algoritmi di elaborazione delle collezioni e il codice infrastrutturale.
Problema: prima dell'introduzione delle generics era necessario duplicare il codice o utilizzare interfacce vuote (interface{}), il che portava a una perdita di sicurezza dei tipi e a una riduzione delle prestazioni, oltre a complicare il debug.
Soluzione: le generics sono implementate in Go tramite parametri di tipo, che vengono specificati tra parentesi quadre nelle funzioni e nei tipi. Con le constraints è possibile limitare i tipi di parametro consentiti. Questo consente di scrivere funzioni generiche senza perdere la sicurezza dei tipi.
Esempio di codice:
package main import "fmt" type Adder[T any] func(a, b T) T func Sum[T any](slice []T, add Adder[T]) T { var result T for _, v := range slice { result = add(result, v) } return result } func intAdder(a, b int) int { return a + b } func main() { nums := []int{1, 2, 3, 4} sum := Sum(nums, intAdder) fmt.Println(sum) }
Caratteristiche chiave:
È possibile utilizzare operazioni aritmetiche (+, -, *, /) per qualsiasi parametro di tipo T nelle generics?
No. Il compilatore Go non sa se il tipo del parametro supporta le operazioni aritmetiche. Per questo è necessario specificare una constraint, ad esempio un'interfaccia con operator constraints a partire da Go 1.18+.
Esempio di codice:
type Addable interface { int | float64 | uint } func Sum[T Addable](slice []T) T { var result T for _, v := range slice { result += v } return result }
I contenitori generics possono contenere metodi con comportamenti diversi per tipi diversi?
No. I metodi delle strutture o delle funzioni generics sono identici per tutti i tipi, a meno che non si utilizzi type switch all'interno del metodo. Il comportamento deve essere definito tramite constraints o parametrizzato in modo puro.
È possibile creare tipi con parametri di tipo (generic types) a livello di pacchetto, e non solo a livello di funzioni?
Sì, a partire da Go 1.18, è possibile creare sia funzioni generiche che tipi strutturali generici:
type Stack[T any] struct { items []T } func (s *Stack[T]) Push(v T) { s.items = append(s.items, v) }
All'interno di una libreria per la gestione delle collezioni è stata implementata una funzionalità Map universale tramite interface{}:
Pro:
Contro:
Nello stesso progetto si è passato alle generics e si sono imposte limitazioni tramite interfacce:
Pro:
Contro: