En esta página
Funciones y múltiples retornos
Funciones en Go: flexibles y explícitas
Las funciones son los bloques fundamentales de todo programa Go. A diferencia de lenguajes como Python o JavaScript, las funciones de Go son más predecibles: el sistema de tipos estático garantiza que siempre sabrás exactamente qué recibe y qué devuelve cada función.
La característica más importante y diferenciadora de las funciones en Go es la capacidad de retornar múltiples valores. Esta característica está en el corazón del diseño del lenguaje y es fundamental para entender el manejo de errores idiomático.
Declaración básica de funciones
// Función simple
func saludar(nombre string) string {
return "¡Hola, " + nombre + "!"
}
// Sin parámetros ni retorno
func limpiar() {
fmt.Println("Limpiando...")
}
// Parámetros del mismo tipo — forma abreviada
func sumar(a, b int) int {
return a + b
}
// Múltiples tipos de parámetros
func formatear(nombre string, edad int, activo bool) string {
return fmt.Sprintf("%s (%d años, activo: %v)", nombre, edad, activo)
}Múltiples valores de retorno
Esta es la característica que hace que Go sea tan expresivo para el manejo de errores. Una función puede retornar cualquier número de valores:
// Retorna dos valores: resultado y error
func dividir(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("no se puede dividir por cero")
}
return a / b, nil
}
// Retorna tres valores
func minMax(nums []int) (int, int, error) {
if len(nums) == 0 {
return 0, 0, errors.New("slice vacío")
}
min, max := nums[0], nums[0]
for _, n := range nums[1:] {
if n < min {
min = n
}
if n > max {
max = n
}
}
return min, max, nil
}
func main() {
// Debes manejar todos los valores de retorno
resultado, err := dividir(10, 3)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Printf("Resultado: %.2f\n", resultado)
// Ignorar el error con _ (generalmente no recomendado)
min, max, _ := minMax([]int{5, 2, 8, 1, 9, 3})
fmt.Println("Min:", min, "Max:", max)
}Retornos nombrados
Los retornos nombrados te permiten darle nombres a los valores de retorno. Esto tiene dos ventajas: documenta el significado de cada valor y permite el "naked return":
// Los nombres min, max, promedio son documentación Y variables
func analizar(datos []float64) (min, max, promedio float64, err error) {
if len(datos) == 0 {
err = errors.New("se necesitan datos")
return // naked return: retorna el estado actual de min, max, promedio, err
}
min, max = datos[0], datos[0]
suma := 0.0
for _, d := range datos {
suma += d
if d < min {
min = d
}
if d > max {
max = d
}
}
promedio = suma / float64(len(datos))
return // naked return: retorna los valores actuales
}Los retornos nombrados son útiles para funciones que calculan múltiples resultados relacionados, pero evita el abuso de naked returns en funciones largas — pueden reducir la legibilidad.
Funciones variádicas
Las funciones variádicas aceptan un número variable de argumentos del mismo tipo usando ...:
func suma(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
func log(nivel string, partes ...interface{}) {
fmt.Printf("[%s] ", nivel)
fmt.Println(partes...)
}
func main() {
fmt.Println(suma()) // 0
fmt.Println(suma(1)) // 1
fmt.Println(suma(1, 2, 3)) // 6
fmt.Println(suma(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)) // 55
// Pasar un slice a una función variádica con ...
números := []int{10, 20, 30, 40}
fmt.Println(suma(números...)) // 100
log("INFO", "Servidor", "iniciado", "en puerto", 8080)
}Dentro de la función, el parámetro variádico se comporta como un slice. El operador ... al llamar la función expande un slice en argumentos individuales.
Funciones como valores de primera clase
En Go, las funciones son valores. Puedes asignarlas a variables, pasarlas como argumentos y retornarlas:
// Tipo función
type Transformador func(int) int
// Función que acepta y retorna funciones (higher-order function)
func aplicarDosVeces(f Transformador, x int) int {
return f(f(x))
}
func duplicar(n int) int {
return n * 2
}
// Función anónima asignada a variable
triplicar := func(n int) int {
return n * 3
}
func main() {
fmt.Println(aplicarDosVeces(duplicar, 3)) // 12
fmt.Println(aplicarDosVeces(triplicar, 2)) // 18
// Función anónima llamada inmediatamente (IIFE)
resultado := func(a, b int) int {
return a + b
}(5, 3)
fmt.Println(resultado) // 8
}Closures: funciones con estado
Un closure es una función que captura variables de su scope exterior. La función "cierra sobre" esas variables, manteniéndolas vivas incluso después de que el scope exterior haya terminado:
// Factory de contadores — cada llamada crea un contador independiente
func nuevoContador() func() int {
count := 0 // esta variable vive mientras exista la closure
return func() int {
count++
return count
}
}
// Closure para memoización
func memoize(f func(int) int) func(int) int {
cache := make(map[int]int)
return func(n int) int {
if resultado, ok := cache[n]; ok {
return resultado
}
resultado := f(n)
cache[n] = resultado
return resultado
}
}
func fibonacci(n int) int {
if n <= 1 {
return n
}
return fibonacci(n-1) + fibonacci(n-2)
}
func main() {
c1 := nuevoContador()
c2 := nuevoContador() // contador independiente
fmt.Println(c1(), c1(), c1()) // 1 2 3
fmt.Println(c2(), c2()) // 1 2 (independiente de c1)
fibMemo := memoize(fibonacci)
fmt.Println(fibMemo(40)) // rápido gracias al cache
}`defer`: garantizar limpieza de recursos
defer posterga la ejecución de una función hasta que la función que lo contiene retorne. Es la forma idiomática de garantizar que los recursos se liberen correctamente, independientemente de cómo salga la función (retorno normal, error, o panic):
import "os"
func leerArchivo(nombre string) (string, error) {
f, err := os.Open(nombre)
if err != nil {
return "", err
}
defer f.Close() // garantizado: se ejecuta al salir, sin importar qué pase
// leer contenido...
datos := make([]byte, 1024)
n, err := f.Read(datos)
if err != nil {
return "", err // f.Close() se ejecutará igualmente
}
return string(datos[:n]), nil
}Orden LIFO de defer
func demostrarDefer() {
defer fmt.Println("tercero") // se ejecuta primero (LIFO)
defer fmt.Println("segundo") // se ejecuta segundo
defer fmt.Println("primero") // se ejecuta último (fue el último defer registrado, se ejecuta primero)
fmt.Println("función en ejecución")
}
// Output:
// función en ejecución
// primero
// segundo
// terceroDefer con funciones que capturan variables
func contarTiempo(operación string) func() {
inicio := time.Now()
return func() {
fmt.Printf("%s tomó %v\n", operación, time.Since(inicio))
}
}
func operaciónCostosa() {
defer contarTiempo("operaciónCostosa")()
// ... lógica de la función
time.Sleep(100 * time.Millisecond)
}Recursión
Go soporta recursión con el mismo comportamiento que cualquier lenguaje:
func factorial(n int) int {
if n <= 1 {
return 1
}
return n * factorial(n-1)
}
func fibonacci(n int) int {
if n <= 1 {
return n
}
return fibonacci(n-1) + fibonacci(n-2)
}Para recursión profunda, considera usar iteración o técnicas como la acumulación de cola (tail recursion), ya que Go no optimiza tail calls automáticamente.
Con un dominio sólido de las funciones de Go, en la siguiente lección aprenderemos los structs — la forma en que Go organiza datos y comportamiento para modelar el mundo real.
Inicia sesión para guardar tu progreso