Menu

Structs en Golang: definir, inicializar, comparar y etiquetar

Los structs agrupan campos con nombre en un solo tipo. Aprende a definirlos e inicializarlos, los valores cero, los punteros a structs, los structs anónimos, la comparación, los struct tags y la convención del constructor NewX.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

Definir y crear un struct

Un tipo struct es una lista de campos con nombre. Se declara con type Name struct { ... } y los valores se crean con un literal compuesto.

Salida:

Ana 31
{Name:Ana Email:ana@example.com Age:32 Admin:false}
{Name: Email: Age:0 Admin:false}
main.User{Name:"Bob", Email:"", Age:0, Admin:false}

%+v imprime los nombres de los campos y %#v imprime sintaxis de Go. Los dos son la forma más rápida de inspeccionar un struct al depurar.

Los campos del mismo tipo pueden compartir línea: X, Y float64. Los nombres de campo con mayúscula inicial son exportados (visibles para otros paquetes y para encoding/json); los que van en minúscula, no.

Formas del literal

User{Name: "Ana", Age: 31}        // named fields: order-free, omitted fields are zero
User{"Ana", "a@x.com", 31, false} // positional: every field, in order
&User{Name: "Ana"}                // pointer to a new struct
new(User)                         // pointer to a zeroed struct

Prefiere los campos con nombre. Los literales posicionales se rompen cuando alguien añade o reordena un campo, y go vet avisa de ellos en tipos struct de otros paquetes (por ejemplo net/url.Error struct literal uses unkeyed fields). La forma posicional está bien para tipos locales diminutos como Point{1, 2}.

Sin valores por defecto: valores cero y constructores

Go no tiene valores por defecto en los campos ni constructores en el lenguaje. Todos los campos empiezan con el valor cero de su tipo: 0, "", false, nil. Dos idiomas cubren el hueco.

Haz que el valor cero sea útil. sync.Mutex, bytes.Buffer y strings.Builder funcionan sin inicialización. Cuando puedas diseñar un tipo así, hazlo.

Escribe una función NewX cuando un campo necesite un valor por defecto distinto de cero o haya que reservar algo, como un map:

NewServer devuelve un puntero porque el servidor tiene métodos que lo modifican y va a compartirse. Devolver el struct por valor también es común en tipos pequeños e inmutables (time.Date devuelve un time.Time).

Punteros a structs

Go desreferencia automáticamente los punteros a structs al acceder a los campos. Con p := &User{}, escribes p.Name, no (*p).Name.

Salida:

{11 12}
{11 12} {0 12}
{11 0}

Los structs son valores. La asignación y los argumentos de función los copian; un puntero comparte uno solo. Las copias campo a campo son superficiales: un campo slice o map de la copia sigue apuntando a los mismos datos. Consulta punteros para saber cuándo usar cada cosa.

Structs anónimos

Un tipo struct se puede escribir en línea sin nombre. Es útil para agrupaciones puntuales, tests basados en tablas y para decodificar JSON que solo lees una vez.

La tabla []struct{ in string; want int } es la forma estándar de un test en Go.

Comparar structs

Los structs se pueden comparar con == cuando todos sus campos son comparables. La igualdad significa que todos los campos son iguales. Los structs comparables también pueden ser claves de un map.

Para un struct con campos slice o map, escribe un método Equal o compara en los tests con reflect.DeepEqual.

Struct tags

Un tag es un literal de string detrás del tipo de un campo. El lenguaje lo ignora; las librerías lo leen con reflexión. Quien más lo lee es encoding/json:

Esto imprime {"id":7,"name":"Mug"}: el tag renombra los campos, omitempty quita el Price a cero y el campo no exportado es invisible para el encoder. Los tags siguen la convención key:"value", con varias claves separadas por espacios: `json:"name" db:"user_name"`. Un tag mal formado compila sin problemas y se ignora en silencio, algo que go vet detecta. La página de json cubre las opciones.

Structs vacíos y orden de los campos

struct{} no tiene campos y ocupa cero bytes. Se usa como valor de un set (map[string]struct{}) y como señal en los channels (chan struct{}), donde solo importa el evento.

El orden de los campos afecta a la disposición en memoria: el compilador rellena los campos hasta su alineación. Un struct de bool, int64, bool ocupa 24 bytes en sistemas de 64 bits, mientras que int64, bool, bool ocupa 16. Solo importa en tipos que se guardan en grandes cantidades.

Errores comunes

  • Modificar una copia. for _, u := range users { u.Age++ } cambia una copia cada vez. Usa el índice: users[i].Age++.
  • Olvidar inicializar un campo map. El valor cero del struct contiene un map nil; escribir en él provoca un panic. Resérvalo en un constructor.
  • Copiar un struct que contiene un sync.Mutex. La copia tiene su propio estado del lock. Pasa esos structs por puntero; go vet informa de la copia.
  • Literales posicionales para tipos de otros paquetes. Se rompen cuando se añade un campo.

Preguntas frecuentes

¿Cómo se inicializa un struct en Go?

Con un literal compuesto. Lo habitual es nombrar los campos: u := User{Name: "Ana", Age: 31}, y cualquier campo que omitas recibe su valor cero. User{} da todos los valores cero, &User{...} da un puntero, y new(User) da un puntero a un struct a cero.

¿Admite Go valores por defecto en los campos de un struct?

No. Todos los campos empiezan con el valor cero de su tipo. Lo idiomático es una función constructora, func NewServer() *Server { return &Server{Port: 8080} }, o diseñar el tipo para que su valor cero ya sea útil, como sync.Mutex y bytes.Buffer.

¿Cómo se comparan dos structs en Go?

Con ==, si todos los campos son comparables. Dos valores struct son iguales cuando todos sus campos correspondientes lo son. Un struct que contiene un campo slice, map o función no se puede comparar con == (error de compilación); compara tú los campos o usa reflect.DeepEqual en los tests.

¿Qué son los struct tags en Go?

Literales de string detrás del tipo de un campo, como `json:"name,omitempty"`. El compilador los ignora; paquetes como encoding/json, los drivers de bases de datos y los validadores los leen con reflexión para controlar cómo se codifican, nombran o comprueban los campos.

¿Cómo imprimo un struct con los nombres de sus campos en Go?

Usa fmt.Printf("%+v\n", s), que imprime {Name:Ana Age:31}. %v imprime solo los valores, y %#v imprime sintaxis de Go incluido el nombre del tipo: main.User{Name:"Ana", Age:31}.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR