Los genéricos de Go 1.18 abrieron la puerta a patrones más sofisticados que van más allá de las funciones Map y Filter básicas. Go 1.21 incorporó los paquetes slices, maps y cmp en la biblioteca estándar, consolidando las operaciones más comunes sobre colecciones sin necesidad de dependencias externas.
El paquete slices
slices cubre las operaciones que antes requerían implementación manual: ordenar, buscar, comparar y transformar slices de cualquier tipo:
package main
import (
"cmp"
"fmt"
"slices"
)
func main() {
nums := []int{5, 2, 8, 1, 9, 3}
// Ordenar (in-place)
slices.Sort(nums)
fmt.Println(nums) // [1 2 3 5 8 9]
// Búsqueda binaria
idx, encontrado := slices.BinarySearch(nums, 5)
fmt.Printf("5 en índice %d: %vn", idx, encontrado)
// Comparar dos slices elemento a elemento
a := []int{1, 2, 3}
b := []int{1, 2, 4}
fmt.Println(slices.Compare(a, b)) // -1 (a < b)
// Contiene
fmt.Println(slices.Contains(nums, 8)) // true
// Eliminar duplicados (requiere slice ordenado)
con := []int{1, 1, 2, 3, 3, 4}
fmt.Println(slices.Compact(con)) // [1 2 3 4]
// Revertir
slices.Reverse(nums)
fmt.Println(nums) // [9 8 5 3 2 1]
}
Ordenar structs con slices.SortFunc
type Producto struct {
Nombre string
Precio float64
Stock int
}
func main() {
productos := []Producto{
{"Ratón", 29.99, 100},
{"Teclado", 79.99, 50},
{"Monitor", 349.99, 15},
{"Auriculares", 49.99, 75},
}
// Ordenar por precio ascendente
slices.SortFunc(productos, func(a, b Producto) int {
return cmp.Compare(a.Precio, b.Precio)
})
for _, p := range productos {
fmt.Printf("%-15s %.2fn", p.Nombre, p.Precio)
}
// Ordenar por nombre, luego por precio para empates
slices.SortFunc(productos, func(a, b Producto) int {
if n := cmp.Compare(a.Nombre, b.Nombre); n != 0 {
return n
}
return cmp.Compare(a.Precio, b.Precio)
})
}
El paquete maps
import "maps"
func main() {
original := map[string]int{"a": 1, "b": 2, "c": 3}
// Clonar un mapa
copia := maps.Clone(original)
// Obtener todas las claves
for k := range maps.Keys(original) { // Go 1.23+: iterador
fmt.Print(k, " ")
}
// Copiar pares de un mapa a otro
destino := map[string]int{"d": 4}
maps.Copy(destino, original)
fmt.Println(destino) // map[a:1 b:2 c:3 d:4]
// Borrar entradas que cumplen una condición
maps.DeleteFunc(destino, func(k string, v int) bool {
return v < 2
})
fmt.Println(destino) // map[b:2 c:3 d:4]
// Comparar dos mapas
fmt.Println(maps.Equal(original, copia)) // true
}
El paquete cmp
cmp.Compare devuelve -1, 0 o 1 según si el primer argumento es menor, igual o mayor. cmp.Or devuelve el primer valor no-zero de la lista:
import "cmp"
// Compare: para usar en SortFunc y comparaciones
fmt.Println(cmp.Compare(3, 5)) // -1
fmt.Println(cmp.Compare("b", "a")) // 1
fmt.Println(cmp.Compare(4.0, 4.0)) // 0
// Or: primer valor no-zero (muy útil para defaults)
nombre := cmp.Or("", "valor por defecto", "otro") // "valor por defecto"
fmt.Println(nombre)
El operador ~ en constraints
El operador ~ incluye tipos definidos sobre el tipo base, no solo el tipo exacto:
type MiInt int // tipo nuevo basado en int
type Entero interface {
~int | ~int32 | ~int64
}
// Con ~int, MiInt cumple el constraint
// Sin ~, MiInt no cumpliría porque no ES int, sino un tipo nuevo
func Doble[T Entero](v T) T {
return v * 2
}
func main() {
var x MiInt = 5
fmt.Println(Doble(x)) // 10
}
Error clásico: type switch sobre parámetro de tipo
// INCORRECTO: no puedes hacer type switch sobre un parámetro de tipo
func procesarGenerico[T any](v T) {
switch v.(type) { // Error de compilación
case int:
fmt.Println("entero")
}
}
// CORRECTO: convertir a any primero
func procesarGenerico[T any](v T) {
switch any(v).(type) {
case int:
fmt.Println("entero")
case string:
fmt.Println("cadena")
default:
fmt.Printf("tipo desconocido: %Tn", v)
}
}
Los paquetes slices y maps de Go 1.21 ya implementan internamente este patrón, por lo que en la práctica rara vez necesitas escribir tus propias versiones genéricas de estas operaciones.
