Múltiples Retornos, Retornos Nombrados y Retornos Desnudos
Las funciones de Go pueden devolver múltiples valores, usualmente un resultado y un error, y opcionalmente pueden nombrar esos resultados en la firma.
Busca en todas las páginas de la documentación
Las funciones de Go pueden devolver múltiples valores, usualmente un resultado y un error, y opcionalmente pueden nombrar esos resultados en la firma.
Los retornos nombrados permiten sentencias return desnudas y se integran con defer, pero su uso excesivo oculta el flujo de datos y frustra a los lectores.
Los valores de retorno múltiples son el principal mecanismo de Go para señalar fallos sin excepciones.
Los llamadores reciben tuplas (T, error) y manejan los errores explícitamente en cada paso.
Los parámetros de resultado nombrados declaran variables con ámbito en todo el cuerpo de la función, inicializadas a sus valores cero al entrar.
Un return desnudo (sin operandos) devuelve los valores actuales de esos resultados nombrados.
Usa los retornos nombrados con moderación: brillan en funciones cortas con defer que ajustan un error, y perjudican la legibilidad en funciones largas con muchas ramas.
Prefiere los retornos sin nombre cuando la lógica sea más fácil de seguir con sentencias explícitas de return valor, err.
Tarjeta de receta de referencia rápida, lista para copiar y pegar.
func ReadConfig(path string) ([]byte, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("leer configuración %s: %w", path, err)
}
return data, nil
}
// Retornos nombrados + defer: ajusta err al salir (patrón idiomático).
func WriteAtomic(path string, data []byte) (err error) {
f, err := os.CreateTemp(filepath.Dir(path), "tmp-*")
if err != nil {
return err
}
defer func() {
closeErr := f.Close()
if err == nil {
err = closeErr
}
}()
if _, err = io.Copy(f, bytes.NewReader(data)); err != nil {
return err
}
return os.Rename(f.Name(), path)
}Cuándo usar esto:
error como último valor.defer necesitan actualizar el error saliente después de la limpieza (defer + err nombrado).return.package config
import (
"encoding/json"
"fmt"
"os"
)
type Settings struct {
Port int `json:"port"`
Host string `json:"host"`
}
func Load(path string) (s Settings, err error) {
raw, err := os.ReadFile(path)
if err != nil {
return s, fmt.Errorf("config: leer: %w", err)
}
if err = json.Unmarshal(raw, &s); err != nil {
return s, fmt.Errorf("config: decodificar: %w", err)
}
if s.Port == 0 {
return s, fmt.Errorf("config: puerto requerido")
}
return s, nil
}Lo que esto demuestra:
%w para errors.Is / errors.As.Settings cero junto con un error; los llamadores deben verificar err antes de usar s.err nombrado podría combinarse con defer en cargadores más complejos; aquí los retornos explícitos siguen siendo más claros.0, "", nil) antes de que se ejecute la primera sentencia.return sin argumentos no asigna nada nuevo; sale con los valores nombrados actuales.return liste explícitamente los valores; el compilador impone la aridad.| Estilo | Elegir cuándo | Evitar cuándo |
|---|---|---|
Sin nombre (T, error) | La mayoría de la lógica de negocio, muchas ramas | Nunca; este es el predeterminado |
Nombrado + return vals explícito | Claridad en godoc para APIs con muchas tuplas | El cuerpo de la función excede ~40 líneas |
Nombrado + return desnudo | Ajuste corto de defer/error | Múltiples escritores a los mismos nombres de resultado |
Nombrado solo para err | defer cierra recursos y establece err | err sombreado en bloques internos |
err Nombradofunc do() (err error) {
defer func() {
if err != nil {
err = fmt.Errorf("do: %w", err)
}
}()
// ...
return nil
}La clausura diferida observa la variable de resultado nombrada por referencia (dirección), por lo que las asignaciones en defer afectan el valor devuelto a los llamadores.
Sombra err con := en un bloque interno rompe este patrón; usa = en el resultado nombrado en su lugar.
// Anti-patrón: retorno desnudo lejos de la declaración.
func parseAll(input string) (tokens []string, err error) {
for _, part := range strings.Split(input, ",") {
if part == "" {
return // ¿qué valores? el lector debe desplazarse hacia arriba
}
tokens = append(tokens, part)
}
return
}Prefiere return tokens, nil al final y después de los errores para facilitar la lectura.
err nombrado con :=: El foo, err := interno crea un nuevo err que defer no actualizará. Solución: usa = en el resultado nombrado existente o evita := en err.return x, y explícito excepto en ayudantes cortos de defer.err y usar s de todos modos. Solución: documenta "indefinido en caso de error" o devuelve punteros (*Settings, error).defer/error lo requiera.error personalizado que recopile fallos.| Alternativa | Usar cuándo | No usar cuándo |
|---|---|---|
Retorno de struct único Result | Muchas salidas correlacionadas | Solo se necesita (T, error) - ruido extra de struct |
Parámetros de salida de puntero func(*T) error | Interoperabilidad C o mutación de estado grande | Bibliotecas idiomáticas de Go - prefiere retornos |
panic/recover | Errores de programador verdaderamente irrecuperables | Fallos esperados (I/O, validación) |
Tupla (T, bool) ok | Búsquedas en mapas, comprobaciones de presencia | Operaciones que fallan con errores ricos |
Los errores son valores en el flujo de control normal.
Los llamadores ven las rutas de fallo en orden fuente sin desenrollado oculto de la pila.
return sin operandos en una función que declara parámetros de resultado nombrados.
Devuelve lo que sea que esas variables contengan al salir.
Sí, a sus valores cero de tipo al inicio de la función, antes de que se ejecuten las sentencias.
Cuando la limpieza (cierre, reversión) debe ejecutarse y puede fallar después de un error principal.
El err nombrado permite que defer aumente o preserve el primer fallo.
Rara vez.
Nombra err para patrones defer; mantén los otros resultados sin nombre a menos que godoc realmente se beneficie.
No; los tipos de resultado son fijos en la firma.
Usa interfaces, structs o genéricos para resultados variantes.
Más allá de tres valores correlacionados, prefiere una struct de resultado.
La explosión de aridad hace que los sitios de llamada sean propensos a errores.
La convención coloca error al final: (T, error) o (int, string, error).
Los linters y lectores esperan esa posición.
Válido para punteros o interfaces cuando "no encontrado" no es un error.
Documenta la semántica; algunos llamadores confunden nil, nil con errores.
Sin diferencia significativa; son un mecanismo en tiempo de compilación.
Las preocupaciones de legibilidad dominan la decisión.
Se comportan como locales ordinarios para la cobertura.
Las guías de estilo (incluidas las reglas de golangci-lint revive) pueden marcar los retornos desnudos.
Los manejadores no devuelven nada; escriben en http.ResponseWriter.
Extrae la lógica en funciones que devuelven (T, error) y mapea errores a códigos de estado en el manejador.
nil, nil a través de interfacesVersiones de Stack: Esta página fue escrita para Go 1.26.x (predeterminado GC Green Tea, go fix modernizers - verificar parche en la compilación), chi (última - verificar en la compilación), gin (última - verificar en la compilación), echo (última - verificar en la compilación), google.golang.org/grpc (última - verificar en la compilación), sigs.k8s.io/controller-runtime (última - verificar en la compilación), kubebuilder (última - verificar en la compilación), tinygo (última - verificar objetivos de placa en la compilación), wazero (última - verificar en la compilación) y golangci-lint (última - verificar conjunto de linters en la compilación).
Revisado por Chris St. John·Última actualización: 19 jul 2026