Structs en Go: definición, instanciación y campos anónimos (embedding)

Los structs son la forma que tiene Go de agrupar datos relacionados bajo un mismo tipo. No hay clases ni herencia, pero sí composición a través de campos anónimos y métodos definidos fuera del struct. Esta guía cubre desde la definición básica hasta los struct tags que usa el codificador JSON.

Definir e instanciar un struct

package main

import "fmt"

type Persona struct {
    Nombre string
    Edad   int
    Email  string
}

func main() {
    // literal con nombres de campo
    p1 := Persona{
        Nombre: "Ana",
        Edad:   30,
        Email:  "[email protected]",
    }

    // literal posicional (desaconsejado: frágil si cambia el orden)
    p2 := Persona{"Luis", 25, "[email protected]"}

    fmt.Println(p1.Nombre) // Ana
    fmt.Println(p2.Edad)   // 25
}

Campos exportados y privados

En Go, la visibilidad se controla con la primera letra del identificador. Mayúscula = exportado (visible fuera del paquete); minúscula = privado (solo visible en el mismo paquete):

type Cuenta struct {
    Titular string  // exportado
    saldo   float64 // privado
}

func NuevaCuenta(titular string, saldoInicial float64) *Cuenta {
    return &Cuenta{Titular: titular, saldo: saldoInicial}
}

func (c *Cuenta) Saldo() float64 {
    return c.saldo
}

Punteros a struct

Para evitar copiar structs grandes o para que los métodos puedan modificar el original, usa punteros:

func envejecer(p *Persona) {
    p.Edad++ // Go desreferencia automáticamente
}

func main() {
    p := &Persona{Nombre: "Marta", Edad: 29}
    envejecer(p)
    fmt.Println(p.Edad) // 30
}

Embedding: composición en lugar de herencia

Un struct puede incluir otro struct sin darle nombre explícito. Los campos y métodos del struct embebido se promocionan al struct externo:

type Animal struct {
    Nombre string
}

func (a Animal) Describir() string {
    return "Animal: " + a.Nombre
}

type Perro struct {
    Animal          // embedding, sin nombre de campo
    Raza  string
}

func main() {
    d := Perro{
        Animal: Animal{Nombre: "Rex"},
        Raza:   "Labrador",
    }
    fmt.Println(d.Nombre)     // Rex (campo promocionado)
    fmt.Println(d.Describir()) // Animal: Rex (método promocionado)
    fmt.Println(d.Raza)       // Labrador
}

Struct tags para JSON

Los struct tags son cadenas literales que los codificadores pueden leer vía reflexión. El paquete encoding/json los usa para mapear campos del struct a claves del JSON:

import (
    "encoding/json"
    "fmt"
)

type Producto struct {
    ID     int     `json:"id"`
    Nombre string  `json:"nombre"`
    Precio float64 `json:"precio,omitempty"`
    oculto string  // campos privados no se serializan nunca
}

func main() {
    p := Producto{ID: 1, Nombre: "Teclado", Precio: 0}
    datos, _ := json.Marshal(p)
    fmt.Println(string(datos))
    // {"id":1,"nombre":"Teclado"}
    // Precio omitido porque es 0 y tiene omitempty
}

El error del bucle range con structs

Este es el error más frecuente al iterar sobre un slice de structs: la variable de iteración es una copia, no el original:

personas := []Persona{
    {Nombre: "Ana", Edad: 20},
    {Nombre: "Luis", Edad: 25},
}

// MAL: modifica la copia, no el slice
for _, p := range personas {
    p.Edad += 10
}
fmt.Println(personas[0].Edad) // 20, sin cambios

// BIEN: usa índice para modificar el original
for i := range personas {
    personas[i].Edad += 10
}
fmt.Println(personas[0].Edad) // 30

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP