- Published on
Introducción a los Genéricos en Go
- Authors
- Name
- Gonzalo Gramaglia
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.
interface{}
antes de los Genéricos
Uso de 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.
interface{}
:
Ejemplo utilizando 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.
*/
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.