Structs são tipos de dados compostos em Go que agrupam campos de diferentes tipos sob um mesmo nome. Elas são fundamentais para modelar entidades do mundo real, como pessoas, produtos ou registros de banco de dados. Diferente de arrays ou slices que armazenam elementos homogêneos, structs permitem heterogeneidade.

Nesta aula, aprenderemos a declarar structs, definir seus campos, inicializá-las de várias formas e usar structs anônimas para situações pontuais.

Definição

Uma struct é definida com a palavra-chave type seguida do nome e da palavra struct. A declaração fica geralmente no nível do pacote, mas pode ser local. Exemplo:

type Pessoa struct {
    Nome  string
    Idade int
}

Após a definição, podemos usar Pessoa como um tipo qualquer. A struct criada é um tipo nomeado, o que facilita a reutilização e documentação.

Campos

Campos são as variáveis que compõem a struct. Cada campo tem um nome e um tipo. Eles podem ser de qualquer tipo, inclusive outras structs, slices, maps, etc. Acessamos os campos usando a notação ponto (.).

var p Pessoa
p.Nome = "João"
p.Idade = 30
fmt.Println(p.Nome) // João

Campos podem ter tags (metadados) usadas por bibliotecas como JSON, mas isso é assunto avançado. Por ora, foque no básico.

Inicialização

Existem várias formas de inicializar uma struct:

  • Literal com campos na ordem: p := Pessoa{"Maria", 25} (requer todos os campos na ordem declarada).
  • Literal com campos nomeados: p := Pessoa{Nome: "Carlos", Idade: 40} (mais seguro e legível).
  • Usando new: p := new(Pessoa) retorna um ponteiro para uma struct com zero values.
  • Declaração com var: var p Pessoa inicializa com zero values.

Exemplo completo:

package main

import "fmt"

type Endereco struct {
    Rua    string
    Numero int
}

type Pessoa struct {
    Nome     string
    Idade    int
    Endereco Endereco
}

func main() {
    p1 := Pessoa{
        Nome:  "Ana",
        Idade: 28,
        Endereco: Endereco{
            Rua:    "Av. Brasil",
            Numero: 100,
        },
    }
    fmt.Println(p1.Nome, p1.Endereco.Rua)

    var p2 Pessoa
    p2.Nome = "Pedro"
    p2.Idade = 35
    fmt.Println(p2)
}

Structs anônimas

Structs anônimas são declaradas sem um nome, geralmente usadas para uso único, como em testes ou configurações rápidas. A sintaxe é struct { campos }.

p := struct {
    Nome  string
    Idade int
}{
    Nome:  "João",
    Idade: 30,
}
fmt.Println(p)

Também podem ser usadas dentro de outras structs ou como tipo de campo. Por exemplo:

type Produto struct {
    Nome  string
    Preco float64
    Info  struct {
        Descricao string
        Categoria string
    }
}

Structs anônimas são úteis para evitar a criação de tipos nomeados desnecessários, mas uso excessivo pode prejudicar a legibilidade.

Boas práticas

  • Use inicialização com campos nomeados sempre que possível, para evitar erros de ordem.
  • Evite structs muito grandes; considere dividir em structs menores.
  • Documente campos públicos com comentários.
  • Structs anônimas são boas para escopos pequenos, mas prefira tipos nomeados se houver reuso.

Exercícios

  1. Crie uma struct Carro com campos Marca (string), Modelo (string) e Ano (int). Inicialize-a com valores literais e imprima cada campo.

    ✓ Resposta:
    package main
    
    import "fmt"
    
    type Carro struct {
        Marca  string
        Modelo string
        Ano    int
    }
    
    func main() {
        c := Carro{"Ford", "Fiesta", 2020}
        fmt.Println("Marca:", c.Marca)
        fmt.Println("Modelo:", c.Modelo)
        fmt.Println("Ano:", c.Ano)
    }
  2. Defina uma struct Livro com campos Titulo, Autor e Paginas. Crie uma variável do tipo Livro usando var e atribua valores aos campos depois. Imprima a struct.

    ✓ Resposta:
    package main
    
    import "fmt"
    
    type Livro struct {
        Titulo  string
        Autor   string
        Paginas int
    }
    
    func main() {
        var l Livro
        l.Titulo = "1984"
        l.Autor = "George Orwell"
        l.Paginas = 328
        fmt.Println(l)
    }
  3. Crie uma struct anônima para representar um ponto 2D (coordenadas X e Y) e inicialize-a com valores. Imprima as coordenadas.

    ✓ Resposta:
    package main
    
    import "fmt"
    
    func main() {
        ponto := struct {
            X int
            Y int
        }{
            X: 10,
            Y: 20,
        }
        fmt.Printf("Ponto: (%d, %d)\n", ponto.X, ponto.Y)
    }
  4. Defina uma struct Funcionario com campos Nome, Salario e Endereco (outra struct com Rua e Numero). Inicialize usando campos nomeados e acesse o endereço completo.

    ✓ Resposta:
    package main
    
    import "fmt"
    
    type Endereco struct {
        Rua    string
        Numero int
    }
    
    type Funcionario struct {
        Nome     string
        Salario  float64
        Endereco Endereco
    }
    
    func main() {
        f := Funcionario{
            Nome:    "Maria",
            Salario: 5000.00,
            Endereco: Endereco{
                Rua:    "Rua das Flores",
                Numero: 123,
            },
        }
        fmt.Printf("%s mora na %s, %d\n", f.Nome, f.Endereco.Rua, f.Endereco.Numero)
    }
  5. Crie um slice de structs Aluno (com campos Nome e Nota) e adicione três alunos. Percorra o slice e imprima o nome de cada aluno com nota maior que 7.

    ✓ Resposta:
    package main
    
    import "fmt"
    
    type Aluno struct {
        Nome string
        Nota float64
    }
    
    func main() {
        alunos := []Aluno{
            {"João", 8.5},
            {"Maria", 6.0},
            {"Pedro", 9.2},
        }
        for _, a := range alunos {
            if a.Nota > 7.0 {
                fmt.Println(a.Nome)
            }
        }
    }

Referências