> serie: aprende go de 0 a 100 | artículo 4 de 19
Las funciones en Go parecen las de cualquier lenguaje hasta que devuelven dos cosas a la vez y no pasa nada raro. Hoy cubrimos la sintaxis, los retornos múltiples, las variádicas, las closures y defer, el mejor amigo de tus recursos abiertos.
Sintaxis: el tipo va detrás
// tipo después del nombre, como en los diagramas UML
func suma(a int, b int) int {
return a + b
}
// parámetros consecutivos del mismo tipo se agrupan
func suma(a, b int) int {
return a + b
}
El tipo después del nombre choca los primeros diez minutos y luego se lee mejor, sobre todo en firmas complejas. La justificación oficial está en el blog de Go (Go’s Declaration Syntax). Otra diferencia con Java: no hay sobrecarga de funciones. Un nombre, una firma (FAQ oficial).
Retornos múltiples: el patrón que lo cambia todo
Una función Go puede devolver varios valores sin envoltorios: ni tuplas de Python, ni arrays destructurados de JS, ni clases Result (A Tour of Go, multiple results y Effective Go):
// Dos valores de vuelta: resultado y error.
// Este es el patrón de Go. Grábatelo.
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("división entre cero")
}
return a / b, nil
}
// En el sitio de la llamada:
resultado, err := divide(10, 3)
if err != nil {
// gestionar y salir pronto
}
Ese dúo (valor, error) es la columna vertebral del manejo de errores en Go y tiene artículo propio (el 9). De momento quédate con la forma: se devuelve el error como valor normal y se comprueba con un if a la vuelta.
Los retornos también pueden llevar nombre. Úsalo con moderación, en funciones cortas documenta, en largas despista.
Variádicas: los tres puntos
El equivalente al *args de Python o al rest de JS (especificación, function types):
// ...int: cero o más enteros. Dentro es un slice.
func maximo(nums ...int) int {
m := nums[0]
for _, n := range nums[1:] {
if n > m {
m = n
}
}
return m
}
maximo(3, 7, 2)
valores := []int{3, 7, 2}
maximo(valores...) // un slice se "despliega" con ...
Dentro de la función, nums es un slice normal. Y ojo al detalle de la última línea: … también funciona al revés, desplegando un slice en argumentos individuales. fmt.Println es variádica, por eso traga lo que le eches.
Closures: funciones con mochila
Las funciones son valores de primera clase: se asignan, se pasan y se devuelven. Y cuando una función devuelta captura variables de su entorno, tienes una closure (A Tour of Go, closures):
func contador() func() int {
n := 0
return func() int {
n++ // captura n por referencia
return n
}
}
c1 := contador()
c2 := contador()
fmt.Println(c1(), c1(), c1()) // 1 2 3
fmt.Println(c2()) // 1 (estado propio)
Cada llamada a contador() crea un n nuevo e independiente que sobrevive entre llamadas. Si vienes de JS esto es terreno conocido. Si vienes de Java, es lo que las lambdas quisieron ser cuando fueran mayores: aquí la captura es por referencia y sin exigir final.
defer: la limpieza garantizada
defer aplaza una llamada hasta que la función que la contiene retorna, por el camino que sea, incluido un error a mitad (A Tour of Go, defer y Effective Go, defer):
func leeArchivo(ruta string) error {
f, err := os.Open(ruta)
if err != nil {
return err
}
defer f.Close() // se ejecuta al salir, pase lo que pase
// ... trabajar con f tranquilamente ...
return nil
}
Es el try/finally de Java o el with de Python, pero pegado a la línea que abre el recurso: abres y en la línea siguiente dejas programado el cierre. Imposible olvidarlo 40 líneas después, que es justo el punto fuerte de las funciones en Go con defer.
Detalle clave: los defer apilados se ejecutan en orden LIFO, el último registrado sale primero. Y los argumentos se evalúan en el momento del defer, no al ejecutarse.

Reto de la semana: pon a prueba las funciones en Go
- Escribe divide(a, b float64) (float64, error) y gestiona la división entre cero en el sitio de la llamada.
- Crea una variádica media(nums …float64) float64 y llámala con argumentos sueltos y con un slice desplegado.
- Construye una closure acumulador() que sume lo que le pases en cada llamada y demuestra que dos acumuladores no comparten estado.
- Bonus: apila tres defer con fmt.Println y predice el orden de salida antes de ejecutar.
Funciones en Go: nivel completado.
En el próximo artículo: structs, métodos y por qué Go no necesita clases para vivir bien.
> exit