Una función variádica en Go es una función que acepta un número variable de argumentos del mismo tipo. defer, por su parte, nos permite retrasar una acción hasta que la función termina.
Cerramos el bloque de funciones con dos herramientas habituales.
Primero, responderemos a una pregunta que quizás te hayas hecho: ¿Cómo es posible que fmt.Println acepte a veces un argumento, a veces dos, y a veces veinte? La respuesta son las Funciones Variádicas.
Segundo, abordaremos un problema clásico de la programación: olvidarse de cerrar un archivo o liberar una conexión a base de datos. Para eso, Go nos da una palabra clave muy práctica: defer.
Funciones variádicas (...)
Una función variádica puede aceptar un número variable de argumentos.
Para definirla, usamos los tres puntos ... antes del tipo del último parámetro.
func Sumar(numeros ...int) int {
total := 0
// Dentro de la función, 'numeros' se comporta como un Slice []int
for _, n := range numeros {
total += n
}
return total
}
func main() {
fmt.Println(Sumar(1)) // 1
fmt.Println(Sumar(1, 2, 3)) // 6
fmt.Println(Sumar(10, 20, 30, 40)) // 100
}El parámetro dentro de la función
Go es muy pragmático. Cuando usas ...int, lo que hace el compilador es empaquetar todos los argumentos que le pases en un Slice de tipo []int. Por eso podemos iterar sobre él con range.
Expandir un slice (...)
¿Y si ya tenemos los números en un slice y queremos pasárselos a una función variádica?
No podemos pasar el slice directamente (Sumar(miSlice) dará error). Tenemos que “desempaquetarlo” o “explotarlo” usando ... después de la variable.
miSlice := []int{10, 20, 30}
// Sumar(miSlice) // ERROR: cannot use []int as int
Sumar(miSlice...) // CORRECTO: Pasa los elementos uno a unoSolo puede haber un parámetro variádico y debe ser el último de la lista de argumentos.
func Error(a ...int, b string) ❌ Esto no compila.
defer: programar una llamada para el final
Imagina este escenario clásico: Abres un archivo, escribes en él y… ocurre un error a mitad de camino y la función retorna antes de tiempo. El archivo se queda abierto. Esto es una fuga de recursos (resource leak).
En C++ usaríamos destructores (RAII), en Java o C# usaríamos bloques try-catch-finally.
En Go, usamos defer.
La sentencia defer pospone la ejecución de una función hasta que la función que la contiene está a punto de retornar.
El patrón de abrir y cerrar
En Go conviene acostumbrarse a esto: en cuanto adquieras un recurso, programa su liberación inmediatamente.
package main
import (
"fmt"
"os"
)
func LeerArchivo() {
archivo, err := os.Open("datos.txt")
if err != nil {
panic(err)
}
// Este es el detalle importante.
// Programamos el cierre YA, aunque el archivo se usará más abajo.
defer archivo.Close()
// ... hacemos cosas con el archivo ...
// ... leemos datos ...
// ... procesamos ...
// Al llegar al final de la función (o si hay un return antes),
// Go ejecutará archivo.Close() automáticamente.
}Así, la apertura y el cierre quedan juntos visualmente. La llamada diferida también se ejecutará si la función termina por un panic, antes de que este siga propagándose.
En operaciones de escritura, Close puede devolver un error importante. Un simple defer archivo.Close() lo descarta; si necesitamos detectarlo, debemos comprobar ese error mediante un retorno nombrado o cerrar explícitamente en el punto adecuado.
Orden de ejecución LIFO
Si pones varios defer en una misma función, se ejecutan en orden LIFO (Last In, First Out), es decir, como una pila: el último en entrar es el primero en salir.
Es igual que una pila de platos: el último que colocamos encima es el primero que retiramos.
func main() {
fmt.Println("Inicio")
defer fmt.Println("1. Primer defer")
defer fmt.Println("2. Segundo defer")
defer fmt.Println("3. Tercer defer")
fmt.Println("Fin")
}Salida:
Inicio
Fin
3. Tercer defer
2. Segundo defer
1. Primer deferDefer y los valores de los argumentos
Cuidado con esto: los argumentos de la función diferida se evalúan en el momento en que se declara el defer, no cuando se ejecuta.
x := 10
defer fmt.Println("Valor diferido:", x) // Captura x = 10 AHORA
x = 20
fmt.Println("Valor final:", x)Salida:
Valor final: 20
Valor diferido: 10Ejemplo combinado: medir el tiempo
Podemos usar defer con funciones anónimas para crear una pequeña utilidad de trazabilidad:
func RastrearTiempo(nombre string) func() {
start := time.Now()
fmt.Printf("Iniciando %s...\n", nombre)
// Devolvemos una función que, al ejecutarse, calculará el tiempo final
return func() {
fmt.Printf("Saliendo de %s. Tiempo: %v\n", nombre, time.Since(start))
}
}
func ProcesoLargo() {
// RastrearTiempo devuelve la función de cierre.
// Con defer, aseguramos que se ejecute al final.
defer RastrearTiempo("ProcesoLargo")()
time.Sleep(2 * time.Second)
fmt.Println("Trabajando...")
}