Structs

Structs

Imagine que você está preenchendo um formulário de informações pessoais: nome, idade, número de telefone, endereço — essas informações estão relacionadas entre si e juntas descrevem uma pessoa. Em Go, uma struct é esse "formulário": ela combina múltiplos campos de diferentes tipos em um tipo composto personalizado. Comparado a maps, os campos de uma struct são determinados em tempo de compilação, tornando-os seguros em tipo e semanticamente claros. Structs são a forma central de organizar dados em Go.


1. Conceitos Principais

Conceito Descrição
Definição type NomeStruct struct { ... }, o nome do campo vem antes do tipo
Inicialização Literal S{c1: v1, c2: v2}, new(S) retorna um ponteiro, &S{}
Acesso a Campos Notação ponto s.Campo, ponteiros também usam notação ponto p.Campo (não é necessário ->)
Tipo por Valor Structs são tipos por valor; atribuição e argumentos de função copiam a struct inteira
Receptor por Ponteiro Obtenha um ponteiro via &s; modificar a struct através do ponteiro afeta o valor original
Campos Anônimos Escreva apenas o tipo sem nome de campo; o nome do tipo se torna o nome do campo, permitindo "composição"
Structs Aninhadas Campos de struct podem ser structs, suportando aninhamento em múltiplos níveis
Tags de Struct Metadados envoltos em crases, comumente usados para mapeamento JSON/banco de dados
⚠️ Nota: Structs são tipos por valor; passá-las como argumentos cria uma cópia. Para modificar o valor original, passe um ponteiro *S.


2. Sintaxe/Uso Básico

Definindo uma Struct

GO
// Define uma struct Pessoa
type Pessoa struct {
    Nome string
    Idade int
    Cidade string
}
💡 Dica: Em Go, se o nome de um campo de struct começa com letra maiúscula, ele é exportado (público); se começa com letra minúscula, é não exportado (privado) e visível apenas dentro do pacote.

Métodos de Inicialização

GO
// Método 1: nome do campo: valor (recomendado, boa legibilidade, ordem não importa)
p1 := Pessoa{Nome: "Alice", Idade: 25, Cidade: "São Paulo"}

// Método 2: atribuir por ordem (não recomendado para muitos campos, propenso a erros)
p2 := Pessoa{"Bob", 30, "Rio de Janeiro"}

// Método 3: declarar primeiro, depois atribuir
var p3 Pessoa
p3.Nome = "Charlie"
p3.Idade = 28
p3.Cidade = "Belo Horizonte"

// Método 4: criar com new (retorna ponteiro)
p4 := new(Pessoa)  // *Pessoa
p4.Nome = "Diana"

// Método 5: pegar endereço (retorna ponteiro)
p5 := &Pessoa{Nome: "Eve", Idade: 22, Cidade: "Curitiba"}
💡 Dica: Uma struct declarada com var p Pessoa inicializa todos os campos com o valor zero de seus respectivos tipos (string → "", int → 0, bool → false).

Acessando e Modificando Campos

GO
p := Pessoa{Nome: "Alice", Idade: 25}

// Ler um campo
fmt.Println(p.Nome)  // Alice

// Modificar um campo
p.Idade = 26
fmt.Println(p.Idade)   // 26
💡 Dica: A sintaxe para acessar campos através de ponteiros e valores é idêntica — Go automaticamente desreferencia, então não é necessário p->Nome.

Ponteiros de Struct

GO
p := &Pessoa{Nome: "Alice", Idade: 25}

// Go automaticamente desreferencia, use notação ponto diretamente
fmt.Println(p.Nome)  // Alice

// Modificar o valor original através do ponteiro
p.Idade = 30
💡 Dica: Go não tem operador ->. Se p é um valor ou um ponteiro, você sempre usa p.Campo para acessar campos.


3. Código de Exemplo

Exemplo 1: Uso Básico (Dificuldade ⭐)

Definir uma struct Livro, criar instâncias e imprimir informações.

GO
package main

import "fmt"

// Livro define uma struct de livro
type Livro struct {
    Titulo  string
    Autor   string
    Paginas int
    Preco   float64
}

func main() {
    // Criar usando um literal
    livro1 := Livro{
        Titulo:  "The Go Programming Language",
        Autor:   "Alan Donovan",
        Paginas: 350,
        Preco:   59.9,
    }

    // Imprimir a struct
    fmt.Println(livro1)

    // Acessar campos individuais
    fmt.Printf("Titulo: %s\n", livro1.Titulo)
    fmt.Printf("Autor: %s\n", livro1.Autor)
    fmt.Printf("Preco: %.1f\n", livro1.Preco)

    // Modificar um campo
    livro1.Preco = 49.9
    fmt.Printf("Novo preco: %.1f\n", livro1.Preco)

    // Criar por ordem (não recomendado)
    livro2 := Livro{"Python Crash Course", "Eric Matthes", 280, 45.0}
    fmt.Println(livro2)
}
▶ Experimente

Saída:

TEXT
{The Go Programming Language Alan Donovan 350 59.9}
Titulo: The Go Programming Language
Autor: Alan Donovan
Preco: 59.9
Novo preco: 49.9
{Python Crash Course Eric Matthes 280 45}

Exemplo 2: Uso Avançado (Dificuldade ⭐⭐)

Usando ponteiros, structs aninhadas e campos anônimos.

GO
package main

import "fmt"

// Endereco struct
type Endereco struct {
    Cidade  string
    Rua     string
    CEP     string
}

// Funcionario struct (usa campo anônimo Endereco)
type Funcionario struct {
    Nome     string
    Idade    int
    Endereco        // Campo anônimo: nome do tipo é o nome do campo
    Salario  float64
}

func main() {
    // ========== Inicialização de struct aninhada ==========
    func1 := Funcionario{
        Nome:    "Alice",
        Idade:   30,
        Salario: 15000,
        Endereco: Endereco{
            Cidade: "São Paulo",
            Rua:    "Rua Augusta, 100",
            CEP:    "01310-100",
        },
    }

    fmt.Printf("Funcionario: %s\n", func1.Nome)
    fmt.Printf("Cidade: %s\n", func1.Endereco.Cidade)  // Acesso explícito

    // ========== Acesso "promovido" a campo anônimo ==========
    // Campos anônimos podem ser acessados diretamente pelo nome do tipo
    fmt.Printf("Rua: %s\n", func1.Rua)   // Mesmo que func1.Endereco.Rua
    fmt.Printf("CEP: %s\n", func1.CEP)   // Mesmo que func1.Endereco.CEP

    // ========== Operações com ponteiros ==========
    funcPtr := &func1

    // Modificar através do ponteiro (Go automaticamente desreferencia)
    funcPtr.Idade = 31
    funcPtr.Salario = 18000
    funcPtr.Cidade = "Rio de Janeiro" // Modificar campo anônimo através do ponteiro

    fmt.Printf("\nApós modificação: %+v\n", *funcPtr)

    // ========== Tipo por valor vs Tipo por ponteiro ==========
    fmt.Println("\n--- Tipo por valor vs Tipo por ponteiro ---")

    // Tipo por valor: atribuição é cópia
    func2 := func1
    func2.Nome = "Bob"
    fmt.Printf("func1.Nome:  %s\n", func1.Nome)  // Ainda Alice
    fmt.Printf("func2.Nome: %s\n", func2.Nome) // Bob

    // Tipo por ponteiro: atribuição é compartilhamento
    func3 := funcPtr
    func3.Nome = "Charlie"
    fmt.Printf("funcPtr.Nome: %s\n", funcPtr.Nome) // Charlie (modificado)
}
▶ Experimente

Saída:

TEXT
Funcionario: Alice
Cidade: São Paulo
Rua: Rua Augusta, 100
CEP: 01310-100

Após modificação: {Nome:Alice Idade:31 Endereco:{Cidade:Rio de Janeiro Rua:Rua Augusta, 100 CEP:01310-100} Salario:18000}

--- Tipo por valor vs Tipo por ponteiro ---
func1.Nome:  Alice
func2.Nome: Bob
funcPtr.Nome: Charlie

Exemplo 3: Aplicação Completa (Dificuldade ⭐⭐⭐)

Usando tags de struct para serialização/deserialização JSON e construindo um sistema de gerenciamento de alunos.

GO
package main

import (
    "encoding/json"
    "fmt"
    "sort"
    "strings"
)

// Aluno struct com tags JSON
type Aluno struct {
    ID     int      `json:"id"`
    Nome   string   `json:"nome"`
    Idade  int      `json:"idade"`
    Nota   float64  `json:"nota"`
    Tags   []string `json:"tags,omitempty"` // omitempty: omitir quando vazio
    secreto string   // Campo não exportado, não acessível via JSON
}

// SalaDeAula (struct aninhada)
type SalaDeAula struct {
    NomeDaSala string   `json:"nome_da_sala"`
    Alunos     []Aluno  `json:"alunos"`
}

// AdicionarAluno adiciona um aluno
func (s *SalaDeAula) AdicionarAluno(a Aluno) {
    s.Alunos = append(s.Alunos, a)
}

// ObterTopN obtém os top N alunos por nota
func (s *SalaDeAula) ObterTopN(n int) []Aluno {
    // Fazer uma cópia para evitar modificar os dados originais
    ordenado := make([]Aluno, len(s.Alunos))
    copy(ordenado, s.Alunos)

    // Ordenar por nota em ordem decrescente
    sort.Slice(ordenado, func(i, j int) bool {
        return ordenado[i].Nota > ordenado[j].Nota
    })

    if n > len(ordenado) {
        n = len(ordenado)
    }
    return ordenado[:n]
}

// Media calcula a nota média
func (s *SalaDeAula) Media() float64 {
    if len(s.Alunos) == 0 {
        return 0
    }
    total := 0.0
    for _, a := range s.Alunos {
        total += a.Nota
    }
    return total / float64(len(s.Alunos))
}

// BuscarPorNome busca por nome (correspondência parcial)
func (s *SalaDeAula) BuscarPorNome(palavraChave string) []Aluno {
    var resultado []Aluno
    for _, a := range s.Alunos {
        if strings.Contains(a.Nome, palavraChave) {
            resultado = append(resultado, a)
        }
    }
    return resultado
}

func main() {
    // Criar uma sala de aula
    sala := &SalaDeAula{NomeDaSala: "Turma 1, Ano 2024"}

    // Adicionar alunos
    alunos := []Aluno{
        {ID: 1, Nome: "João Silva", Idade: 18, Nota: 92.5, Tags: []string{"bom em matemática", "monitor"}},
        {ID: 2, Nome: "Maria Santos", Idade: 19, Nota: 88.0, Tags: []string{"bom em esportes"}},
        {ID: 3, Nome: "Pedro Oliveira", Idade: 18, Nota: 95.5},
        {ID: 4, Nome: "Ana Costa", Idade: 20, Nota: 78.0, Tags: []string{"talento artístico"}},
        {ID: 5, Nome: "Lucas Pereira", Idade: 19, Nota: 91.0, Tags: []string{"bom em matemática", "vencedor de competição"}},
    }

    for _, a := range alunos {
        sala.AdicionarAluno(a)
    }

    // ========== Serialização JSON ==========
    fmt.Println("=== Serialização JSON ===")
    jsonData, err := json.MarshalIndent(sala, "", "  ")
    if err != nil {
        fmt.Println("Falha na serialização:", err)
    } else {
        fmt.Println(string(jsonData))
    }

    // ========== Deserialização JSON ==========
    fmt.Println("\n=== Deserialização JSON ===")
    jsonStr := `{"id":6,"nome":"Carlos Souza","idade":17,"nota":97.0,"tags":["bom em inglês"]}`
    var novoAluno Aluno
    if err := json.Unmarshal([]byte(jsonStr), &novoAluno); err != nil {
        fmt.Println("Falha na deserialização:", err)
    } else {
        fmt.Printf("Novo aluno: %+v\n", novoAluno)
    }

    // ========== Demonstração de Recursos ==========
    fmt.Println("\n=== Informações da Turma ===")
    fmt.Printf("Turma: %s, total de %d alunos\n", sala.NomeDaSala, len(sala.Alunos))
    fmt.Printf("Nota média: %.1f\n", sala.Media())

    fmt.Println("\n=== Top 3 Notas ===")
    top3 := sala.ObterTopN(3)
    for i, a := range top3 {
        fmt.Printf("  #%d: %s (%.1f)\n", i+1, a.Nome, a.Nota)
    }

    fmt.Println("\n=== Buscar 'Silva' ===")
    resultados := sala.BuscarPorNome("Silva")
    for _, a := range resultados {
        fmt.Printf("  %s (ID: %d, Nota: %.1f)\n", a.Nome, a.ID, a.Nota)
    }
}
▶ Experimente

Saída:

TEXT
=== Serialização JSON ===
{
  "nome_da_sala": "Turma 1, Ano 2024",
  "alunos": [
    {
      "id": 1,
      "nome": "João Silva",
      "idade": 18,
      "nota": 92.5,
      "tags": [
        "bom em matemática",
        "monitor"
      ]
    },
    {
      "id": 2,
      "nome": "Maria Santos",
      "idade": 19,
      "nota": 88
    },
    {
      "id": 3,
      "nome": "Pedro Oliveira",
      "idade": 18,
      "nota": 95.5
    },
    {
      "id": 4,
      "nome": "Ana Costa",
      "idade": 20,
      "nota": 78,
      "tags": [
        "talento artístico"
      ]
    },
    {
      "id": 5,
      "nome": "Lucas Pereira",
      "idade": 19,
      "nota": 91,
      "tags": [
        "bom em matemática",
        "vencedor de competição"
      ]
    }
  ]
}

=== Deserialização JSON ===
Novo aluno: {ID:6 Nome:Carlos Souza Idade:17 Nota:97 Tags:[bom em inglês]}

=== Informações da Turma ===
Turma: Turma 1, Ano 2024, total de 5 alunos
Nota média: 89.0

=== Top 3 Notas ===
  #1: Pedro Oliveira (95.5)
  #2: João Silva (92.5)
  #3: Lucas Pereira (91.0)

=== Buscar 'Silva' ===
  João Silva (ID: 1, Nota: 92.5)

3. Cenários de Aplicação Comuns

Cenário 1: Interação com Dados JSON

Em desenvolvimento web, structs são a forma padrão de tratar requisições/respostas JSON. Tags controlam os nomes dos campos JSON.

GO
package main

import (
    "encoding/json"
    "fmt"
)

// RespostaAPI estrutura de resposta unificada da API
type RespostaAPI struct {
    Codigo   int         `json:"codigo"`
    Mensagem string      `json:"mensagem"`
    Dados    interface{} `json:"dados,omitempty"`
}

// UsuarioDTO objeto de transferência de dados do usuário
type UsuarioDTO struct {
    ID       int    `json:"id"`
    Nome     string `json:"nome"`
    Email    string `json:"email"`
    Senha    string `json:"-"` // "-" significa ignorar este campo no JSON
}

func main() {
    // Construir uma resposta
    usuario := UsuarioDTO{
        ID:    1,
        Nome:  "alice",
        Email: "alice@exemplo.com",
        Senha: "senha123", // Não será serializado
    }

    resp := RespostaAPI{
        Codigo:   200,
        Mensagem: "sucesso",
        Dados:    usuario,
    }

    // Serializar
    jsonBytes, _ := json.MarshalIndent(resp, "", "  ")
    fmt.Println(string(jsonBytes))

    // Deserializar
    jsonStr := `{"codigo":404,"mensagem":"Usuário não encontrado"}`
    var errResp RespostaAPI
    json.Unmarshal([]byte(jsonStr), &errResp)
    fmt.Printf("\nCódigo de erro: %d, Mensagem: %s\n", errResp.Codigo, errResp.Mensagem)
}

Saída:

TEXT
{
  "codigo": 200,
  "mensagem": "sucesso",
  "dados": {
    "id": 1,
    "nome": "alice",
    "email": "alice@exemplo.com"
  }
}

Código de erro: 404, Mensagem: Usuário não encontrado

Cenário 2: Simulação Orientada a Objetos (Composição em Vez de Herança)

Go não tem classes ou herança; a reutilização de código é alcançada através de aninhamento de structs (composição).

GO
package main

import "fmt"

// Logger capacidade de logging
type Logger struct {
    Prefixo string
}

// Log exibe uma mensagem de log
func (l *Logger) Log(msg string) {
    fmt.Printf("[%s] %s\n", l.Prefixo, msg)
}

// Cache capacidade de cache
type Cache struct {
    dados map[string]string
}

// Obter recupera do cache
func (c *Cache) Obter(chave string) (string, bool) {
    val, ok := c.dados[chave]
    return val, ok
}

// Definir define uma entrada no cache
func (c *Cache) Definir(chave, valor string) {
    if c.dados == nil {
        c.dados = make(map[string]string)
    }
    c.dados[chave] = valor
}

// ServicoUsuario reutiliza as capacidades Logger e Cache através de composição
type ServicoUsuario struct {
    Logger       // Embute Logger
    Cache        // Embute Cache
    Nome string
}

func main() {
    svc := ServicoUsuario{
        Logger: Logger{Prefixo: "ServicoUsuario"},
        Nome:   "Serviço de Usuário",
    }

    // Chamar métodos embutidos diretamente
    svc.Log("Serviço iniciado")              // De Logger
    svc.Definir("usuario:1", "Alice")         // De Cache
    svc.Log("Cache inicializado")

    if val, ok := svc.Obter("usuario:1"); ok {
        svc.Log(fmt.Sprintf("Usuário encontrado: %s", val))
    }
}

Saída:

TEXT
[ServicoUsuario] Serviço iniciado
[ServicoUsuario] Cache inicializado
[ServicoUsuario] Usuário encontrado: Alice

❓ Perguntas Frequentes

P1: Uma struct é um tipo por valor ou tipo por referência?

Structs são tipos por valor. Atribuição e argumentos de função copiam a struct inteira. Para modificar o valor original, passe um ponteiro:

GO
func atualizarIdade(p *Pessoa, idade int) {
    p.Idade = idade  // Modifica diretamente o valor original
}

p := Pessoa{Nome: "Alice", Idade: 25}
atualizarIdade(&p, 30)
fmt.Println(p.Idade)  // 30

P2: Para que servem campos anônimos? Como são diferentes de structs aninhadas?

Campos anônimos implementam "composição". Os métodos e campos do tipo interno são "promovidos" para o tipo externo:

GO
type Endereco struct {
    Cidade string
}

type Pessoa struct {
    Nome string
    Endereco        // Campo anônimo
}

p := Pessoa{Nome: "Alice", Endereco: Endereco{Cidade: "São Paulo"}}
fmt.Println(p.Cidade)         // Acesso direto, mesmo que p.Endereco.Cidade
fmt.Println(p.Endereco.Cidade) // Também funciona com acesso explícito

Se structs aninhadas tiverem campos com o mesmo nome, você precisa usar o caminho completo para desambiguar.

P3: O que são tags de struct? Como usá-las?

Tags são metadados envoltos em crases, comumente usados para controlar o comportamento de JSON, ORM de banco de dados e outras bibliotecas:

GO
type Usuario struct {
    ID       int    `json:"id"              db:"user_id"`
    Nome     string `json:"nome"            db:"user_name"`
    Senha    string `json:"-"               db:"password"`    // Ignorado no JSON
    Email    string `json:"email,omitempty"  db:"email"`       // Omitido no JSON quando vazio
}

Tags comuns:

P4: Structs podem ser comparadas?

Depende dos tipos dos campos. Se todos os campos forem tipos comparáveis (int, string, bool, arrays, etc.), structs podem ser comparadas com == e !=. Se contiverem campos incomparáveis como slices, maps ou funcs, não podem ser comparadas diretamente:

GO
type Ponto struct { X, Y int }

p1 := Ponto{1, 2}
p2 := Ponto{1, 2}
fmt.Println(p1 == p2)  // true

// Structs contendo slices não podem usar ==
type Dados struct { Itens []int }
// d1 == d2  // Erro de compilação!

📖 Resumo


📝 Exercícios

Exercício 1 (⭐)

Definir uma struct Retangulo com campos Largura e Altura. Implementar o seguinte:

  1. Criar uma instância de Retangulo e imprimir
  2. Escrever um método Area() para calcular a área
  3. Escrever um método Perimetro() para calcular o perímetro
  4. Escrever um método EhQuadrado() para verificar se é um quadrado

Exercício 2 (⭐⭐)

Definir uma struct ContaBancaria com campos Titular (string) e Saldo (float64). Implementar o seguinte:

  1. Depositar(valor) para depósitos
  2. Sacar(valor) para saques (notificar quando saldo insuficiente)
  3. Transferir(outra *ContaBancaria, valor) para transferências
  4. Usar tags JSON para serializar informações da conta (ocultar saldo usando json:"-")

Exercício 3 (⭐⭐⭐)

Implementar um sistema de gerenciamento de inventário de produtos:

  1. Definir uma struct Produto (ID, Nome, Preco, Estoque, Tags)
  2. Definir uma struct Inventario (NomeDaLoja, slice de Produtos)
  3. Implementar métodos: AdicionarProduto, RemoverProduto(id), BuscarPorTag(tag), ValorTotal() (valor total do inventário)
  4. Implementar serialização JSON com requisitos: Preco com duas casas decimais, omitir Estoque quando 0
  5. Criar instâncias, adicionar produtos, buscar por tag, calcular valor total e saída como JSON

Próxima Lição

👉 08-methods - Métodos

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%