Golang

Structs: definição, inicialização e campos embutidos Já leu

8 min de leitura

Structs: definição, inicialização e campos embutidos
Go não é uma linguagem orientada a objetos no sentido tradicional. Não existem classes, não existe herança, não existe

Go não é uma linguagem orientada a objetos no sentido tradicional. Não existem classes, não existe herança, não existe construtor obrigatório. O que existe é a struct — uma estrutura que agrupa campos relacionados sob um único tipo nomeado. Combinada com métodos, interfaces e composição, a struct é o bloco fundamental com o qual todo design em Go é construído.

Quem vem de Java ou C# pode estranhar a ausência de classes no início, mas rapidamente percebe que structs com métodos oferecem o mesmo poder com menos cerimônia e mais clareza.


Definindo uma struct

A palavra-chave type define um novo tipo nomeado. Combinada com struct, cria um tipo composto com campos:

type Pessoa struct {
    Nome  string
    Idade int
    Email string
}

Por convenção, nomes de tipos em Go usam PascalCase. Campos que começam com letra maiúscula são exportados — visíveis fora do pacote. Campos com letra minúscula são privados ao pacote onde foram definidos.


Inicializando structs

Há três formas de criar um valor do tipo struct.

Literal com nomes de campos — forma recomendada:

p := Pessoa{
    Nome:  "Ricardo",
    Idade: 35,
    Email: "ricardo@exemplo.com",
}

Essa forma é preferida porque é explícita, resistente a mudanças na ordem dos campos e auto-documentada.

Literal posicional — evite em código real:

p := Pessoa{"Ricardo", 35, "ricardo@exemplo.com"}

Funciona, mas quebra silenciosamente se um campo for adicionado ou reordenado. Use apenas para structs muito pequenas e estáveis, e nunca em código exportado.

Declaração com valor zero:

var p Pessoa
fmt.Println(p) // { 0 }

Todos os campos recebem seus valores zero: strings viram "", ints viram 0, bools viram false. Go garante que toda struct é sempre inicializada, nunca contém lixo de memória.


Acessando e modificando campos

O acesso usa a notação de ponto:

p := Pessoa{Nome: "Ana", Idade: 28, Email: "ana@exemplo.com"}

fmt.Println(p.Nome)   // Ana
fmt.Println(p.Idade)  // 28

p.Idade = 29
p.Email = "ana.silva@exemplo.com"

fmt.Println(p) // {Ana 29 ana.silva@exemplo.com}

Structs e ponteiros

Como visto no artigo sobre ponteiros, structs são passadas por valor. Para modificar uma struct dentro de uma função ou evitar cópias custosas, use ponteiros:

func aniversario(p *Pessoa) {
    p.Idade++
}

func main() {
    p := Pessoa{Nome: "Carlos", Idade: 40}
    aniversario(&p)
    fmt.Println(p.Idade) // 41
}

Go permite criar um ponteiro diretamente com o operador & em um literal de struct:

p := &Pessoa{
    Nome:  "Diana",
    Idade: 32,
    Email: "diana@exemplo.com",
}

fmt.Println(p.Nome)  // Diana — Go desreferencia automaticamente

Structs anônimas

Go permite declarar structs sem dar um nome ao tipo. São úteis para dados temporários, testes e respostas JSON pontuais:

endereco := struct {
    Rua    string
    Cidade string
    CEP    string
}{
    Rua:    "Av. Paulista, 1000",
    Cidade: "São Paulo",
    CEP:    "01310-100",
}

fmt.Println(endereco.Cidade) // São Paulo

Campos embutidos: composição em vez de herança

Go não tem herança, mas tem embedding — a capacidade de embutir um tipo dentro de outro. Quando um tipo é embutido, seus campos e métodos são promovidos ao tipo externo, ficando acessíveis como se fossem seus próprios:

type Endereco struct {
    Rua    string
    Cidade string
    Estado string
}

type Empresa struct {
    Nome    string
    CNPJ    string
    Endereco           // campo embutido — sem nome explícito
}

func main() {
    e := Empresa{
        Nome: "Tech Ltda",
        CNPJ: "12.345.678/0001-99",
        Endereco: Endereco{
            Rua:    "Rua das Flores, 42",
            Cidade: "Curitiba",
            Estado: "PR",
        },
    }

    // Acesso promovido — como se fossem campos de Empresa
    fmt.Println(e.Cidade)  // Curitiba
    fmt.Println(e.Estado)  // PR

    // Acesso explícito — também funciona
    fmt.Println(e.Endereco.Rua) // Rua das Flores, 42
}

O campo embutido pode ser acessado pelo nome do tipo (e.Endereco) ou diretamente pelos campos promovidos (e.Cidade). A promoção é apenas sintática — internamente, Cidade ainda pertence a Endereco.


Embedding com múltiplos tipos

Um tipo pode embutir vários outros tipos simultaneamente:

type Auditoria struct {
    CriadoEm     string
    AtualizadoEm string
    CriadoPor    string
}

type Produto struct {
    ID    int
    Nome  string
    Preco float64
    Auditoria
}

func main() {
    p := Produto{
        ID:    1,
        Nome:  "Notebook",
        Preco: 4500.00,
        Auditoria: Auditoria{
            CriadoEm:     "2024-01-15",
            AtualizadoEm: "2024-03-10",
            CriadoPor:    "admin",
        },
    }

    fmt.Println(p.Nome)       // Notebook
    fmt.Println(p.CriadoEm)   // 2024-01-15 — promovido de Auditoria
    fmt.Println(p.CriadoPor)  // admin
}

Esse padrão é muito usado para adicionar campos de auditoria, metadados ou comportamentos reutilizáveis a múltiplos tipos sem duplicação.


Conflito de nomes em embedding

Quando dois tipos embutidos têm campos com o mesmo nome, o acesso pelo nome promovido torna-se ambíguo e o compilador exige o caminho completo:

type A struct{ Nome string }
type B struct{ Nome string }

type C struct {
    A
    B
}

func main() {
    c := C{
        A: A{Nome: "nome de A"},
        B: B{Nome: "nome de B"},
    }

    // fmt.Println(c.Nome) — erro: ambiguous selector c.Nome
    fmt.Println(c.A.Nome) // nome de A
    fmt.Println(c.B.Nome) // nome de B
}

Tags de struct

Go permite adicionar metadados aos campos de uma struct usando tags — strings literais colocadas após o tipo do campo. As tags são ignoradas pelo compilador mas são acessíveis via reflection, e pacotes como encoding/json, gorm e validate as utilizam extensivamente:

type Usuario struct {
    ID    int    `json:"id"`
    Nome  string `json:"nome"`
    Email string `json:"email,omitempty"`
    Senha string `json:"-"`
}

Nesse exemplo:

  • json:"id" instrui o pacote encoding/json a usar "id" como chave no JSON em vez de "ID"
  • omitempty omite o campo do JSON se seu valor for zero
  • json:"-" exclui o campo completamente da serialização — útil para campos sensíveis como senhas
import (
    "encoding/json"
    "fmt"
)

func main() {
    u := Usuario{ID: 1, Nome: "Ricardo", Email: ""}

    dados, _ := json.Marshal(u)
    fmt.Println(string(dados))
    // {"id":1,"nome":"Ricardo"}
    // Email omitido (omitempty + valor vazio), Senha excluída
}

Comparando structs

Duas structs do mesmo tipo são comparáveis com == se todos os seus campos forem comparáveis. A comparação verifica campo a campo:

type Ponto struct {
    X, Y int
}

p1 := Ponto{1, 2}
p2 := Ponto{1, 2}
p3 := Ponto{3, 4}

fmt.Println(p1 == p2) // true
fmt.Println(p1 == p3) // false

Structs que contêm slices, maps ou funções não são comparáveis com ==. Para esses casos, use reflect.DeepEqual ou implemente uma função de comparação personalizada.


Exemplo prático: modelando um sistema simples

Combinando os conceitos do artigo em um exemplo coeso:

package main

import "fmt"

type Endereco struct {
    Rua    string
    Cidade string
    CEP    string
}

type Cliente struct {
    ID    int
    Nome  string
    Email string
    Endereco
    Ativo bool
}

func novoCliente(id int, nome, email string) *Cliente {
    return &Cliente{
        ID:    id,
        Nome:  nome,
        Email: email,
        Ativo: true,
    }
}

func (c *Cliente) definirEndereco(rua, cidade, cep string) {
    c.Rua    = rua
    c.Cidade = cidade
    c.CEP    = cep
}

func (c Cliente) String() string {
    return fmt.Sprintf("[%d] %s <%s> — %s, %s",
        c.ID, c.Nome, c.Email, c.Rua, c.Cidade)
}

func main() {
    c := novoCliente(1, "Fernanda Lima", "fernanda@exemplo.com")
    c.definirEndereco("Rua XV de Novembro, 500", "Florianópolis", "88010-000")

    fmt.Println(c)
    fmt.Println("Ativo:", c.Ativo)
    fmt.Println("CEP:", c.CEP) // promovido de Endereco
}

Resumo do que foi coberto

Este artigo apresentou structs em Go com profundidade: definição, três formas de inicialização, acesso a campos, structs anônimas, embedding como mecanismo de composição, conflitos de nomes em embedding, tags de struct para serialização e comparação entre structs. Esses conceitos são a base sobre a qual métodos e interfaces — temas dos próximos artigos — serão construídos.


Referências e leituras complementares

Comentários

Mais em Golang

Go Modules: go.mod, go.sum e gerenciamento de dependências
Go Modules: go.mod, go.sum e gerenciamento de dependências

Antes dos m&oacute;dulos, Go usava o GOPATH &mdash; um diret&oacute;rio globa...

Interfaces: contratos implícitos e polimorfismo
Interfaces: contratos implícitos e polimorfismo

&nbsp; Interfaces existem em Java, C#, TypeScript e diversas outras linguage...

Estruturas de controle: if, for e switch
Estruturas de controle: if, for e switch

Todo programa &uacute;til precisa tomar decis&otilde;es e repetir opera&ccedi...