Logo
Published on

Introducción a los Genéricos en Go

Authors

Los genéricos en Go, introducidos en la versión 1.18 (lanzada el 16 de febrero de 2022), permiten escribir funciones y estructuras de datos que pueden trabajar con cualquier tipo de datos de manera segura y eficiente. Esto mejora la reutilización del código sin perder la verificación de tipos en tiempo de compilación.

Go Generics

Uso de interface{} antes de los Genéricos

Antes de los genéricos, se utilizaba interface{} para lograr flexibilidad en los tipos de datos. Sin embargo, esto venía con algunas desventajas, como la pérdida de la verificación de tipos en tiempo de compilación y la necesidad de realizar type assertions.

Ejemplo utilizando interface{}:

package main

import "fmt"

// Función usando interface{}
func Print(a interface{}) {
    switch v := a.(type) {
    case int:
        fmt.Println("int:", v)
    case string:
        fmt.Println("string:", v)
    default:
        fmt.Println("unknown type")
    }
}

func main() {
    Print(123)      // Output: int: 123
    Print("hello")  // Output: string: hello
    Print(3.14)     // Output: unknown type
}

El uso de interface requiere realizar type assertions o type switches para determinar el tipo real del valor en tiempo de ejecución, lo cual puede ser propenso a errores y menos eficiente.

Genéricos: Una Mejor Solución

Con los genéricos, puedes escribir funciones y estructuras que funcionan con cualquier tipo sin perder la verificación de tipos en tiempo de compilación, lo que significa que el compilador puede detectar errores de tipo antes de que el programa se ejecute.

Ejemplo de Funciones Genéricas

package main

import "fmt"

// Definición de una función genérica
func Max[T comparable](a, b T) T {
    if a > b {
        return a
    }
    return b
}

func main() {
    fmt.Println(Max(3, 5))            // Output: 5
    fmt.Println(Max("apple", "pear")) // Output: pear
}
/*
En este ejemplo:
-> `T` es un parámetro de tipo.
-> `comparable` es una restricción que indica que T debe ser un tipo que se puede comparar usando los operadores <, >, ==, etc.
*/

Ejemplo de Estructuras Genéricas

package main

import "fmt"

// Definición de una estructura genérica
type Pair[T, U any] struct {
    First  T
    Second U
}

func main() {
    p1 := Pair[int, string]{First: 1, Second: "one"}
    p2 := Pair[string, float64]{First: "pi", Second: 3.14}

    fmt.Println(p1) // Output: {1 one}
    fmt.Println(p2) // Output: {pi 3.14}
}
/*
En este ejemplo:
-> `T` y `U` son parámetros de tipo.
-> `any` es una restricción que indica que T y U pueden ser de cualquier tipo.
*/

Ejemplo de Métodos Genéricos

package main

import "fmt"

// Definición de una pila genérica
type Stack[T any] struct {
    elements []T
}

// Método para empujar un elemento a la pila
func (s *Stack[T]) Push(element T) {
    s.elements = append(s.elements, element)
}

// Método para sacar un elemento de la pila
func (s *Stack[T]) Pop() T {
    if len(s.elements) == 0 {
        var zero T
        return zero
    }
    element := s.elements[len(s.elements)-1]
    s.elements = s.elements[:len(s.elements)-1]
    return element
}

func main() {
    var intStack Stack[int]
    intStack.Push(1)
    intStack.Push(2)
    fmt.Println(intStack.Pop()) // Output: 2
    fmt.Println(intStack.Pop()) // Output: 1

    var stringStack Stack[string]
    stringStack.Push("hello")
    stringStack.Push("world")
    fmt.Println(stringStack.Pop()) // Output: world
    fmt.Println(stringStack.Pop()) // Output: hello
}

Restricciones de Tipo

Las restricciones de tipo permiten especificar qué tipos se pueden usar como argumentos de tipo en funciones y estructuras genéricas. Se pueden usar interfaces para definir restricciones más complejas.

package main

import "fmt"

// Definición de una restricción personalizada
type Number interface {
    int | int64 | float64
}

// Definición de una función genérica con una restricción personalizada
func Sum[T Number](a, b T) T {
    return a + b
}

func main() {
    fmt.Println(Sum(3, 5))          // Output: 8
    fmt.Println(Sum(3.5, 2.5))      // Output: 6
    // fmt.Println(Sum("a", "b"))   // Error: string no satisface la restricción Number
}
/*
En este ejemplo:
-> `Number` es una restricción que permite solo los tipos int, int64 y float64.
-> La función `Sum` solo puede ser llamada con tipos que satisfagan la restricción Number.
*/

Go Generics

Los genéricos en Go añaden una poderosa herramienta al lenguaje, permitiendo escribir código más flexible y reutilizable sin comprometer la verificación de tipos en tiempo de compilación. Con los genéricos, se pueden crear funciones y estructuras de datos polimórficas de manera más segura y eficiente.