CAP 05 · LEC 03·Composición de datos

Structs: campos, literales y embebido

Los structs son la forma idiomática de agrupar datos en Go. Sin clases ni herencia: solo composición a través de campos y embebido. Aquí cubrimos literales, structs anónimos y el mecanismo de promotion.

● INTERMEDIO7 min lecturapor Fernando Herrera · actualizado mayo de 2026
¿Encontraste un error o algo que mejorar?Editá esta lección en GitHub →

Declarar un struct

Un struct agrupa varios campos bajo un mismo tipo. type Point struct { X, Y int } define un nuevo tipo Point con dos campos enteros. Los nombres que empiezan con mayúscula son exportados (visibles fuera del paquete).

package main import "fmt" // Definición del tipo — fuera de main, normalmente a nivel de paquete type Point struct { X, Y int } type Person struct { Name string Age int Email string } func main() { // Zero value: todos los campos a su zero var p Point fmt.Println(p) // {0 0} fmt.Println(p.X) // 0 // Asignar campos directamente p.X = 3 p.Y = 4 fmt.Println(p) // {3 4} var alice Person alice.Name = "Alice" alice.Age = 30 fmt.Println(alice) // {Alice 30 } }
Salida{0 0} 0 {3 4} {Alice 30 }

Literales: posicional y nombrado

Hay dos formas de construir un struct: posicional (Point{1, 2}) o nombrada (Point{X: 1, Y: 2}). La nombrada es más legible y resiste cambios futuros en el orden de campos.

package main import "fmt" type Point struct { X, Y int } type Person struct { Name string Age int Email string } func main() { // Posicional — tienes que dar TODOS los campos, en orden p1 := Point{3, 4} fmt.Println(p1) // {3 4} // Nombrado — puedes omitir campos (quedan en zero value) p2 := Point{X: 5} fmt.Println(p2) // {5 0} // Nombrado es más claro con muchos campos alice := Person{ Name: "Alice", Age: 30, Email: "alice@example.com", } fmt.Println(alice) // Puntero a struct con & — útil para evitar copias grandes pp := &Point{X: 10, Y: 20} pp.X = 99 // Go aplica auto-deref: (*pp).X = 99 fmt.Println(*pp) // {99 20} }
Salida{3 4} {5 0} {Alice 30 alice@example.com} {99 20}
Prefiere literales nombrados

Los literales posicionales son frágiles: si añades un campo o reordenas, todos los call-sites rompen. En código de producción, usa siempre el formato nombrado.

Structs anónimos

Para datos efímeros (decodificar JSON, configuración local de una función) puedes declarar un struct sin tipo nombrado. Útil cuando el tipo solo se usa en un lugar.

package main import "fmt" func main() { // Struct anónimo: tipo y valor en la misma expresión config := struct { Host string Port int Verbose bool }{ Host: "localhost", Port: 8080, Verbose: true, } fmt.Println(config.Host, config.Port, config.Verbose) // Útil en slices de datos de prueba users := []struct { Name string Age int }{ {"Ana", 30}, {"Bob", 25}, } for _, u := range users { fmt.Printf("%s tiene %d años\n", u.Name, u.Age) } }
Salidalocalhost 8080 true Ana tiene 30 años Bob tiene 25 años

Embebido y promotion

Go no tiene herencia, pero tiene embebido: un campo sin nombre cuyo tipo aporta directamente sus campos y métodos al struct contenedor. A esto se le llama promotion — los campos del tipo embebido se acceden como si fueran propios.

package main import "fmt" type Person struct { Name string Age int } // Employee embebe Person — sin nombre de campo type Employee struct { Person // <-- embebido Salary float64 Role string } func main() { e := Employee{ Person: Person{Name: "Alice", Age: 30}, Salary: 50000, Role: "Engineer", } // Promotion: Name y Age se acceden directamente desde Employee fmt.Println(e.Name) // Alice fmt.Println(e.Age) // 30 fmt.Println(e.Salary) // 50000 // Pero el tipo embebido también está disponible explícitamente fmt.Println(e.Person.Name) // Alice fmt.Println(e.Person) // {Alice 30} // Si Employee define un campo Name propio, ese gana al promovido type Manager struct { Person Name string // sombra el de Person } m := Manager{Person: Person{Name: "interno"}, Name: "Externo"} fmt.Println(m.Name) // Externo — el campo propio gana fmt.Println(m.Person.Name) // interno }
SalidaAlice 30 50000 Alice {Alice 30} Externo interno
Embebido = composición, no herencia

El embebido aporta acceso conveniente a campos y métodos, pero no hay polimorfismo automático. Para polimorfismo se usan interfaces, que veremos más adelante.

Comparación e igualdad

Dos structs son comparables con == si todos sus campos son comparables. La igualdad es campo a campo. Structs con slices o maps no son comparables: usar == da error de compilación.

package main import "fmt" type Point struct{ X, Y int } type Box struct { Name string Items []string // <-- slice: hace Box NO comparable con == } func main() { a := Point{1, 2} b := Point{1, 2} c := Point{3, 4} fmt.Println(a == b) // true fmt.Println(a == c) // false // Structs comparables pueden ser claves de map counter := map[Point]int{} counter[a]++ counter[b]++ // a == b, así que cuenta en la misma clave counter[c]++ fmt.Println(counter) // map[{1 2}:2 {3 4}:1] // Box NO se puede comparar con == (slice dentro) // box1 == box2 // error de compilación _ = Box{Name: "x"} }
Salidatrue false map[{1 2}:2 {3 4}:1]