ProgrammazioneSviluppatore Backend

Come sono implementate le generics in Go? Quali sono i limiti, la sintassi, le insidie e i casi d'uso appropriati?

Supera i colloqui con l'assistente IA Hintsage

Risposta.

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:

  • Sicurezza dei tipi — esclusione degli errori di tipo durante la compilazione.
  • Limitazioni (constraints) — possibilità di limitare le generics solo a tipi che implementano determinate interfacce o a tipi confrontabili.
  • Compatibilità — i nuovi sviluppatori possono utilizzare le generics secondo necessità, senza dover riprogettare i progetti immediatamente.

Domande tranello.

È 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) }

Errori di tipo e anti-pattern

  • Utilizzare interface{} invece delle generics nelle nuove versioni di Go.
  • Dimenticare di specificare le limitazioni (constraints), il che porta all'impossibilità di utilizzare operazioni all'interno del codice universale.
  • Generalizzazione eccessiva di funzioni semplici (generics solo per generalizzazione).

Esempio della vita reale

Caso negativo

All'interno di una libreria per la gestione delle collezioni è stata implementata una funzionalità Map universale tramite interface{}:

Pro:

  • Universale, può essere utilizzato per qualsiasi tipo.

Contro:

  • Mancanza di sicurezza dei tipi, necessità di casting manuale, errori a runtime.

Caso positivo

Nello stesso progetto si è passato alle generics e si sono imposte limitazioni tramite interfacce:

Pro:

  • Sicurezza dei tipi, errori identificati durante la compilazione, semplificazione della manutenzione.

Contro:

  • Richiesta di conoscenza della nuova sintassi, alcuni IDE supportano male constraints complessi.