funciones-go-declaracion-retorno-multiple

Funciones en Go: parámetros, firmas y retorno múltiple

  • 5 min

Una función en Go es un bloque de código reutilizable que realiza una tarea concreta. Puede recibir parámetros, devolver valores o no hacer ninguna de las dos cosas.

Hasta ahora hemos escrito todo nuestro código dentro de main. Pero escribirlo todo en un solo bloque es una receta estupenda para perderse. Necesitamos modularidad.

En Go, las funciones son una unidad básica para organizar y reutilizar el código.

Si vienes de un lenguaje orientado a objetos como Java, verás que las funciones en Go no necesitan vivir dentro de una clase: se declaran a nivel de paquete. Además, pueden devolver más de un valor a la vez.

Declaración básica

La sintaxis es muy limpia. Usamos la palabra clave func.

func Nombre(parametros) TipoRetorno { ... }

package main

import "fmt"

// Función simple que recibe un entero y devuelve un entero
func Doble(numero int) int {
    return numero * 2
}

func main() {
    resultado := Doble(5)
    fmt.Println(resultado) // 10
}
Copied!

Go usa nombres en MixedCaps. Lo que controla la visibilidad es la inicial: una función que empieza por mayúscula está exportada; una que empieza por minúscula solo es accesible dentro de su paquete.

Agrupar parámetros del mismo tipo

Go tiene un pequeño “azúcar sintáctico” muy agradable. Si tienes varios parámetros seguidos del mismo tipo, no necesitas repetir el tipo cada vez. Puedes ponerlo solo al final.

// Forma larga (Verbosa)
func Sumar(a int, b int, c int) int { ... }

// Forma idiomática en Go (Limpia)
func Sumar(a, b, c int) int {
    return a + b + c
}
Copied!

Retorno de valores múltiples

Esta característica aparece con frecuencia en las APIs de Go.

En C# o Java, si quieres devolver dos cosas (por ejemplo, un resultado y un código de error), tienes que:

  1. Crear una clase/struct envoltorio (ResultObject).
  2. O usar parámetros de salida (out en C#).
  3. O devolver una tupla (que suele ser fea de manejar).

En Go, las funciones pueden devolver tantos valores como quieras, separados por comas.

Ejemplo: División con resto

Veamos una función que calcula la división entera y, además, devuelve el resto.

// Devuelve dos enteros: (cociente, resto)
func Dividir(a, b int) (int, int) {
    cociente := a / b
    resto := a % b

    return cociente, resto // Retornamos ambos a la vez
}

func main() {
    // Capturamos ambos valores en variables distintas
    c, r := Dividir(10, 3)

    fmt.Printf("Cociente: %d, Resto: %d\n", c, r)
}
Copied!

El caso de uso real: resultado, error

El 90% de las veces, usarás el retorno múltiple para devolver el valor que buscas y un error. Es el patrón estándar de Go.

func AbrirArchivo(nombre string) (*os.File, error) {
    archivo, err := os.Open(nombre)
    if err != nil {
        return nil, err
    }
    return archivo, nil
}
Copied!

Si asignas el resultado de una función con varios retornos, debes recoger todos sus valores. Usa _ para descartar uno concreto. También puedes ejecutar la llamada como una sentencia y descartar todos sus retornos, aunque no suele tener sentido si no produce otro efecto.

Valores de retorno nombrados

Go permite algo curioso: puedes ponerle nombre a las variables de retorno en la propia firma de la función.

Esto hace dos cosas:

  1. Documenta qué es cada valor de retorno.
  2. Inicializa esas variables automáticamente al principio de la función (con su Zero Value).
  3. Permite escribir un retorno sin argumentos.
// Definimos que vamos a devolver 'area' y 'perimetro'
func Rectangulo(ancho, alto int) (area int, perimetro int) {
    // 'area' y 'perimetro' ya existen aquí e inician en 0

    area = ancho * alto
    perimetro = 2 * (ancho + alto)

    // Return sin argumentos: devuelve los valores actuales de area y perimetro
    return
}
Copied!

¿Debo usarlo?

Conviene usar el retorno sin argumentos solo en funciones muy cortas. En funciones largas, ver un return vacío obliga al lector a subir hasta el principio de la función para recordar qué variables se estaban devolviendo, lo que empeora la legibilidad.

Paso por valor

Es fundamental recordar que, en Go, los argumentos siempre se pasan por valor (copia).

Si pasas una variable a una función, la función recibe una fotocopia. Si la función pinta bigotes en la fotocopia, la foto original no cambia.

func IntentarCambiar(x int) {
    x = 999 // Modifica la copia local
}

func main() {
    valor := 10
    IntentarCambiar(valor)
    fmt.Println(valor) // Sigue siendo 10
}
Copied!

Excepción aparente: si pasas un slice, un mapa o un puntero, también se copia su valor. Sin embargo, esa copia sigue haciendo referencia a los mismos datos. Por tanto, las mutaciones sobre el contenido compartido pueden verse fuera. Cambiar el descriptor del slice, por ejemplo con append, no cambia el descriptor del llamador; para eso debemos devolver el slice actualizado.