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
