generics (parametri de tip) în Go

Metode, interfețe și generics Go 1.18 (metode generice: Go 1.27)

Funcții și tipuri care lucrează cu mai multe tipuri, scrise o singură dată cu parametri de tip [T constrângere].

Sintaxă
func Max[T cmp.Ordered](a, b T) T { ... }

type Stiva[T any] struct {
    elemente []T
}

type Numar interface {
    ~int | ~int64 | ~float64
}

Ce face generics (parametri de tip)

Din Go 1.18, funcțiile și tipurile pot avea parametri de tip, scriși între paranteze drepte: func Suma[T Numar](valori []T) T. La apel, tipul este de obicei dedus din argumente (Suma([]int{1, 2})), dar poate fi dat și explicit (Suma[float64](x)).

Fiecare parametru de tip are o constrângere, adică o interfață care spune ce operații sunt permise: any (orice tip, dar fără operatori), comparable (permite ==, necesar pentru chei de map), cmp.Ordered (permite <) sau o listă de tipuri proprie, ca ~int | ~float64. Semnul ~ include și tipurile definite pe baza lor (de exemplu type Lei float64).

Pachetele slices, maps și cmp sunt scrise cu generics. Din Go 1.27, și metodele pot avea propriii parametri de tip: func (c Cos) Transforma[T any](f func(string) T) []T. Folosește generics când aceeași logică se repetă identic pentru mai multe tipuri; pentru comportamente diferite, interfețele obișnuite rămân mai potrivite.

Exemple

Exemplul 1: Funcție generică cu constrângere proprie

~float64 permite și tipul Lei, definit pe baza lui float64.

package main

import "fmt"

type Numar interface {
    ~int | ~int64 | ~float64
}

type Lei float64

func Suma[T Numar](valori []T) T {
    var total T
    for _, v := range valori {
        total += v
    }
    return total
}

func main() {
    fmt.Println(Suma([]int{9, 7, 10}))
    fmt.Println(Suma([]float64{2.5, 3.25}))
    fmt.Println(Suma([]Lei{19.99, 5.01}))
}

Rezultat

26
5.75
25

Exemplul 2: Tip generic: o stivă

package main

import "fmt"

type Stiva[T any] struct {
    elemente []T
}

func (s *Stiva[T]) Pune(v T) { s.elemente = append(s.elemente, v) }

func (s *Stiva[T]) Scoate() (T, bool) {
    var zero T
    if len(s.elemente) == 0 {
        return zero, false
    }
    ultim := s.elemente[len(s.elemente)-1]
    s.elemente = s.elemente[:len(s.elemente)-1]
    return ultim, true
}

func main() {
    var pagini Stiva[string]
    pagini.Pune("acasă")
    pagini.Pune("produse")
    pagini.Pune("coș")
    for {
        p, ok := pagini.Scoate()
        if !ok {
            break
        }
        fmt.Println("înapoi la:", p)
    }
}

Rezultat

înapoi la: coș
înapoi la: produse
înapoi la: acasă

Exemplul 3: comparable și cmp.Ordered

Numara are nevoie de == (chei de map); Maxim are nevoie de >.

package main

import (
    "cmp"
    "fmt"
)

func Numara[T comparable](valori []T) map[T]int {
    m := make(map[T]int)
    for _, v := range valori {
        m[v]++
    }
    return m
}

func Maxim[T cmp.Ordered](valori ...T) T {
    m := valori[0]
    for _, v := range valori[1:] {
        if v > m {
            m = v
        }
    }
    return m
}

func main() {
    fmt.Println(Numara([]string{"da", "nu", "da", "da"}))
    fmt.Println(Maxim(3, 9, 4), Maxim("Arad", "Iași", "Cluj"))
}

Rezultat

map[da:3 nu:1]
9 Iași

Greșeli frecvente

  • Operatori pe un parametru any: invalid operation: operator + not defined on total (variable of type T constrained by any). Alege o constrângere care îi permite.
  • Numar fără ~: tipurile proprii (Lei) nu sunt acceptate (Lei does not satisfy Numar (possibly missing ~ for float64 in Numar)).
  • Metode cu parametri de tip proprii în Go mai vechi de 1.27: nu compilează (syntax error: method must have no type parameters). Din Go 1.27 sunt permise.

Comenzi înrudite

  • any: Alias pentru interface{}: un tip care poate ține orice valoare; folosit și drept constrângere în generics.
  • comparable: Constrângere predefinită pentru generics: tipurile care se pot compara cu == și != (pot fi chei de map).
  • interface și interfețe: Interfața descrie un set de metode; orice tip care le are o implementează automat, fără declarație explicită.
  • type: Definește un tip nou (pe baza altuia, struct, interfață, funcție) sau un alias pentru un tip existent.
  • min și max: Returnează cel mai mic, respectiv cel mai mare dintre argumente; merg pe numere și texte (Go 1.21).