Estructuras anónimas: cuándo y por qué usarlas

Una estructura anónima es una definición de tipo que no tiene un nombre asignado. En lugar de declarar type User struct { ... } y luego usar User, defines la forma de los datos directamente donde la necesitas.

El diseño de Go busca la simplicidad y evitar el exceso de tipos (boilerplate). Si solo necesitas una forma específica de datos para un proceso temporal, asignarle un nombre global al tipo solo ensucia tu paquete con definiciones que nadie más usará. Por eso, estas estructuras funcionan permitiendo que el compilador entienda la memoria que debe reservar sin necesidad de un identificador en tu espacio de nombres.

Debes usarlas cuando su alcance sea extremadamente limitado: para desestructurar un JSON (unmarshaling) cuando solo te interesan un par de campos de una respuesta enorme, para definir los casos de prueba en un table-driven test, o para agrupar valores relacionados que una función privada necesita devolver. Si intentas exponer una estructura anónima en una función pública (un func GetConfig() struct{ Host string }), rompes la capacidad del usuario para usar tu API. El llamante no podrá declarar una variable con ese tipo, no podrá implementarle interfaces y, lo más importante, la documentación de Go no podrá mostrar el esquema de los datos de forma clara.

Es fundamental no confundirlas con el struct embedding: en el embedding, incluyes un tipo ya existente dentro de otro para promover sus campos; aquí, estamos definiendo un tipo nuevo y sin nombre en el acto.

package main

import (
	"encoding/json"
	"fmt"
)

// Config representa una configuración global con un nombre real.
// Se usa porque es un tipo que otros paquetes necesitarán conocer.
type Config struct {
	Host string
	Port int
}

func main() {
	// 1. Uso en unmarshaling de JSON (datos temporales)
	// El JSON tiene muchos campos, pero solo nos interesan 'id' y 'name'.
	// Definimos la estructura anónima justo aquí para no contaminar el paquete.
	jsonInput := `{"id": 42, "name": "servidor-produccion", "extra": "no-me-importa"}`
	var response struct {
		ID   int    `json:"id"`
		Name string `json:"name"`
	}

	if err := json.Unmarshal([]byte(jsonInput), &response); err != nil {
		panic(err)
	}
	fmt.Printf("Datos extraídos: ID=%d, Name=%s\n", response.ID, response.Name)

	// 2. Uso en table-driven tests (simulado en main)
	// Usamos una estructura anónima para definir los casos de prueba de forma limpia.
	tests := []struct {
		input    string
		expected bool
	}{
		{"hola", true},
		{"", false},
		{"mundo", true},
	}

	fmt.Println("Ejecutando tests...")
	for _, tc := range tests {
		result := validate(tc.input)
		if result != tc.expected {
			fmt.Printf("❌ Error en test con input '%s': se esperaba %v, se obtuvo %v\n", tc.input, tc.expected, result)
		} else {
			fmt.Printf("✅ Test '%s' pasado\n", tc.input)
		}
	}
}

// validate es una función privada que devuelve un valor que podría 
// agruparse en una estructura anónima si fueran más campos.
func validate(s string) bool {
	return s != ""
}

Desglose del ejemplo

En la primera parte, observa cómo response se declara como una struct con campos ID y Name. No hay un type Response struct definido arriba. Esto es ideal para el json.Unmarshal porque la estructura de la respuesta de una API externa suele ser verbosa; si crearas un tipo real para cada respuesta de cada endpoint, tu archivo de código estaría lleno de tipos que solo sirven para mapear JSON.

En el segundo bloque, la variable tests es un slice de estructuras anónimas. En los table-driven tests, esta es la forma estándar de Go para iterar sobre casos de prueba. Al usar una estructura anónima aquí, mantienes la lógica del test contenida y legible, sin necesidad de declarar un tipo TestCase que solo tiene sentido dentro de ese archivo de test.

Finalmente, fíjate en la distinción: Config es un tipo con nombre porque es una entidad de tu dominio que viajará por toda la aplicación. La response y el tc en los tests son solo herramientas de soporte para procesos locales.

El error frecuente

Si intentas usar una estructura anónima en el retorno de una función exportada, causarás un dolor de cabeza innecesario a quienes usen tu código.

// ❌ MAL: Esto hace que la API sea casi imposible de usar.
func GetUser() struct {
	Name string
	Age  int
} {
	return struct {
		Name string
		Age  int
	}{Name: "Alice", Age: 30}
}

// El usuario de tu función no puede hacer esto:
// var u GetUser() // Error de compilación: no puede referenciar el tipo.

Al hacer esto, el consumidor de tu función no puede declarar una variable del tipo que devuelves, ni puede pasar ese tipo como argumento a otras funciones, porque el tipo no tiene nombre para ser referenciado en el código de otros paquetes.

59

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Scroll al inicio