En esta página
Structs y métodos
Structs: la forma de modelar datos en Go
Go no tiene clases. En lugar de eso, usa structs para agrupar datos relacionados, y métodos para asociar comportamiento a esos datos. Este modelo es más simple que la herencia de clases de Java o C#, pero igualmente poderoso cuando se combina con interfaces y embedding.
Si conoces C o Rust, los structs de Go te resultarán familiares. Si vienes de Java o Python, piensa en un struct como una clase, pero sin herencia.
Definiendo structs
// Definición básica
type Persona struct {
Nombre string
Apellido string
Edad int
Email string
}
// Struct anidado
type Dirección struct {
Calle string
Ciudad string
País string
}
type Empleado struct {
Persona // embedding (composición)
Empresa string
Salario float64
Dirección Dirección // campo nombrado (no embedding)
}Los nombres que empiezan con mayúscula son exportados (visibles fuera del paquete). Los que empiezan con minúscula son privados (solo visibles en el mismo paquete).
Creando instancias de structs
// 1. Literal con campos nombrados (recomendado)
p := Persona{
Nombre: "Ana",
Apellido: "García",
Edad: 28,
Email: "[email protected]",
}
// 2. Campos en orden (frágil, evitar)
p2 := Persona{"Luis", "Pérez", 35, "[email protected]"}
// 3. Valor cero — todos los campos inicializados en su zero value
var p3 Persona // Nombre:"", Apellido:"", Edad:0, Email:""
// 4. Pointer al struct
pp := &Persona{Nombre: "María", Edad: 22}
pp.Email = "[email protected]" // Go desreferencia automáticamenteConstructores: funciones factory
Go no tiene constructores especiales. El patrón idiomático es crear funciones New... que validan, inicializan y retornan el struct:
type Rectangulo struct {
Ancho float64
Alto float64
Color string
}
// Constructor que valida
func NuevoRectangulo(ancho, alto float64, color string) (*Rectangulo, error) {
if ancho <= 0 || alto <= 0 {
return nil, fmt.Errorf("dimensiones inválidas: ancho=%.2f, alto=%.2f", ancho, alto)
}
return &Rectangulo{
Ancho: ancho,
Alto: alto,
Color: color,
}, nil
}
// Uso
r, err := NuevoRectangulo(5, 3, "azul")
if err != nil {
fmt.Println("Error:", err)
return
}Métodos: comportamiento asociado a structs
Un método es una función con un receiver — un parámetro especial que indica a qué tipo está asociado:
// Sintaxis: func (receiver TipoReceiver) NombreMétodo(params) (retornos)
type Rectángulo struct {
Ancho, Alto float64
}
// Receiver por valor — recibe una copia del struct
func (r Rectángulo) Área() float64 {
return r.Ancho * r.Alto
}
func (r Rectángulo) Perímetro() float64 {
return 2 * (r.Ancho + r.Alto)
}
// Receiver por puntero — puede modificar el struct original
func (r *Rectángulo) Escalar(factor float64) {
r.Ancho *= factor
r.Alto *= factor
}
func main() {
rect := Rectángulo{Ancho: 5, Alto: 3}
fmt.Println("Área:", rect.Área()) // 15
fmt.Println("Perímetro:", rect.Perímetro()) // 16
rect.Escalar(2) // modifica rect directamente
fmt.Println("Nuevo ancho:", rect.Ancho) // 10
fmt.Println("Nuevo alto:", rect.Alto) // 6
}Receiver por valor vs. por puntero
Esta distinción es fundamental:
type Contador struct {
valor int
}
// Receiver por valor: NO modifica el original
func (c Contador) ValorActual() int {
return c.valor
}
func (c Contador) Incrementar() Contador {
c.valor++
return c // retorna una copia modificada
}
// Receiver por puntero: SÍ modifica el original
func (c *Contador) IncrementarInPlace() {
c.valor++
}
func main() {
c := Contador{valor: 0}
c.IncrementarInPlace() // c.valor = 1
c.IncrementarInPlace() // c.valor = 2
fmt.Println(c.ValorActual()) // 2
nuevoC := c.Incrementar() // c.valor sigue siendo 2
fmt.Println(nuevoC.ValorActual()) // 3
fmt.Println(c.ValorActual()) // 2 (sin cambio)
}Regla práctica: Si algún método necesita puntero, haz que todos los métodos del tipo usen puntero. La consistencia facilita la implementación de interfaces.
Embedding: composición sobre herencia
Go usa composición en lugar de herencia. El embedding permite que los campos y métodos de un struct se "promuevan" a otro:
type Animal struct {
Nombre string
Edad int
}
func (a Animal) Describir() string {
return fmt.Sprintf("%s tiene %d años", a.Nombre, a.Edad)
}
type Perro struct {
Animal // embedding — Perro "hereda" campos y métodos de Animal
Raza string
}
func (p Perro) Ladrar() string {
return "¡Woof!"
}
func main() {
rex := Perro{
Animal: Animal{Nombre: "Rex", Edad: 3},
Raza: "Labrador",
}
// Acceso directo a campos y métodos embebidos
fmt.Println(rex.Nombre) // "Rex" (promovido de Animal)
fmt.Println(rex.Describir()) // "Rex tiene 3 años" (método promovido)
fmt.Println(rex.Ladrar()) // "¡Woof!"
fmt.Println(rex.Raza) // "Labrador"
// También puedes acceder explícitamente
fmt.Println(rex.Animal.Nombre)
}Sobreescribir métodos embebidos
func (p Perro) Describir() string {
// Llama al método embebido y agrega información extra
return p.Animal.Describir() + fmt.Sprintf(", raza: %s", p.Raza)
}Struct tags: metadatos para serialización
Los struct tags son metadatos que se añaden a los campos usando backticks. Son usados por paquetes como encoding/json, encoding/xml, ORMs y frameworks de validación:
type Usuario struct {
ID int `json:"id" db:"user_id"`
Nombre string `json:"nombre" db:"nombre" validate:"required,min=2"`
Email string `json:"email" db:"email" validate:"required,email"`
Contraseña string `json:"-"` // omitir en JSON
CreatedAt time.Time `json:"created_at,omitempty"`
}
// Serialización JSON
u := Usuario{ID: 1, Nombre: "Ana", Email: "[email protected]"}
datos, err := json.Marshal(u)
// {"id":1,"nombre":"Ana","email":"[email protected]"}
// Deserialización JSON
var u2 Usuario
json.Unmarshal([]byte(`{"id":2,"nombre":"Luis","email":"[email protected]"}`), &u2)Los tags más comunes:
json:"nombre"— nombre del campo en JSONjson:"-"— omitir campo en JSONjson:",omitempty"— omitir si el valor es cerodb:"nombre"— nombre de columna en base de datosvalidate:"required"— validación con paquetes comogo-playground/validator
Structs anónimos
Útiles para datos temporales o pruebas:
// Struct anónimo en variable
configuración := struct {
Host string
Puerto int
Debug bool
}{
Host: "localhost",
Puerto: 8080,
Debug: true,
}
// Slice de structs anónimos (común en tests)
casos := []struct {
entrada int
esperado int
}{
{1, 1},
{5, 120},
{10, 3628800},
}Comparación de structs
Los structs son comparables con == si todos sus campos son comparables (no hay slices, maps ni funciones):
type Punto struct{ X, Y int }
p1 := Punto{1, 2}
p2 := Punto{1, 2}
p3 := Punto{3, 4}
fmt.Println(p1 == p2) // true
fmt.Println(p1 == p3) // falseCon el dominio de structs y métodos, en la próxima lección aprenderemos las interfaces — el mecanismo que hace que el polimorfismo en Go sea elegante e implícito.
Inicia sesión para guardar tu progreso