Los structs son el mecanismo principal de Go para agrupar datos relacionados. A diferencia de lenguajes con clases, Go usa composicion sobre herencia mediante structs y embedding.
Declaracion de Struct
Un struct agrupa campos con nombre y tipo:
type Persona struct {
Nombre string
Edad int
Email string
}
func main() {
p := Persona{
Nombre: "Ana",
Edad: 30,
Email: "[email protected]",
}
fmt.Println(p.Nombre) // Ana
}
Campos Exportados vs No Exportados
La visibilidad se controla con la primera letra del campo:
| Convension | Visibilidad | Ejemplo |
|---|---|---|
| Mayuscula | Exportado (publico) | Nombre string |
| Minuscula | No exportado (privado al paquete) | edad int |
type Usuario struct {
Nombre string // Visible fuera del paquete
password string // Solo accesible dentro del paquete
}
Esto aplica tanto a campos como al nombre del struct mismo.
Struct Literals
Hay tres formas de crear un struct:
func main() {
// 1. Con nombres de campo (recomendado)
p1 := Persona{Nombre: "Luis", Edad: 25}
// 2. Sin nombres (fragil, depende del orden)
p2 := Persona{"Luis", 25, "[email protected]"}
// 3. Valor cero (campos en su zero value)
var p3 Persona
fmt.Println(p3.Nombre) // "" (string vacio)
fmt.Println(p3.Edad) // 0
}
Siempre usa la forma con nombres de campo. Es mas legible y resistente a cambios.
Structs Anonimos
Utiles para datos temporales sin necesidad de definir un tipo:
func main() {
config := struct {
Host string
Port int
}{
Host: "localhost",
Port: 8080,
}
fmt.Printf("%s:%d\n", config.Host, config.Port)
}
Son comunes en tests y para deserializar JSON puntual.
Punteros a Structs
Go permite acceder a campos de un puntero sin desreferencia explicita:
func main() {
p := &Persona{Nombre: "Carlos", Edad: 40}
// Go desreferencia automaticamente
fmt.Println(p.Nombre) // equivale a (*p).Nombre
// Modificar a traves del puntero
p.Edad = 41
fmt.Println(p.Edad) // 41
}
Usa punteros cuando necesites modificar el struct o evitar copias costosas.
Embedding (Composicion)
Go favorece composicion sobre herencia. Con embedding, un struct incluye los campos y metodos de otro:
type Direccion struct {
Calle string
Ciudad string
Pais string
}
type Empleado struct {
Nombre string
Direccion // campo embebido (sin nombre)
}
func main() {
e := Empleado{
Nombre: "Maria",
Direccion: Direccion{
Calle: "Av. Principal 123",
Ciudad: "CDMX",
Pais: "Mexico",
},
}
// Acceso directo a campos promovidos
fmt.Println(e.Ciudad) // CDMX
// Tambien accesible de forma explicita
fmt.Println(e.Direccion.Ciudad) // CDMX
}
Si hay conflicto de nombres, debes usar la ruta completa:
type Motor struct {
Potencia int
}
type Chasis struct {
Potencia int // mismo nombre que Motor
}
type Auto struct {
Motor
Chasis
}
func main() {
a := Auto{}
// a.Potencia // ERROR: ambiguo
a.Motor.Potencia = 150 // OK
a.Chasis.Potencia = 200 // OK
}
Struct Tags
Los tags son metadatos asociados a campos, usados por paquetes como encoding/json y ORMs:
type Producto struct {
ID int `json:"id" db:"product_id"`
Nombre string `json:"nombre" db:"name"`
Precio float64 `json:"precio" db:"price"`
EnInventario bool `json:"en_inventario,omitempty"`
Interno string `json:"-"` // excluido del JSON
}
func main() {
p := Producto{
ID: 1,
Nombre: "Laptop",
Precio: 999.99,
}
data, _ := json.Marshal(p)
fmt.Println(string(data))
// {"id":1,"nombre":"Laptop","precio":999.99}
// en_inventario omitido por omitempty (valor cero)
}
| Tag | Uso |
|---|---|
json:"nombre" | Nombre en JSON |
json:"-" | Excluir del JSON |
json:",omitempty" | Omitir si es valor cero |
db:"columna" | Mapeo a columna de BD |
validate:"required" | Validacion con librerias |
Funciones Constructoras (Patron New)
Go no tiene constructores, pero por convencion se usan funciones New:
type Servidor struct {
host string
puerto int
timeout time.Duration
}
func NewServidor(host string, puerto int) *Servidor {
return &Servidor{
host: host,
puerto: puerto,
timeout: 30 * time.Second, // valor por defecto
}
}
func main() {
srv := NewServidor("localhost", 8080)
fmt.Printf("Servidor en %s:%d\n", srv.host, srv.puerto)
}
Este patron permite validar parametros e inicializar valores por defecto.
Comparacion de Structs
Dos structs son comparables si todos sus campos son comparables:
type Punto struct {
X, Y int
}
func main() {
a := Punto{1, 2}
b := Punto{1, 2}
c := Punto{3, 4}
fmt.Println(a == b) // true
fmt.Println(a == c) // false
}
Structs con slices, maps o funciones no son comparables con ==:
type Datos struct {
Valores []int // slice: no comparable
}
// d1 == d2 --> ERROR de compilacion
// Usa reflect.DeepEqual(d1, d2) si lo necesitas
Resumen
| Concepto | Descripcion |
|---|---|
| Struct | Agrupacion de campos con tipo |
| Exportacion | Mayuscula = publico, minuscula = privado |
| Embedding | Composicion: incluir un struct dentro de otro |
| Tags | Metadatos para JSON, BD, validacion |
| Constructor | Funcion NewTipo() que retorna *Tipo |
| Comparacion | Solo si todos los campos son comparables |