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 pacoteencoding/jsona usar"id"como chave no JSON em vez de"ID"omitemptyomite o campo do JSON se seu valor for zerojson:"-"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
-
Especificação da linguagem Go — Struct types — Definição formal de structs, campos e tags. https://go.dev/ref/spec#Struct_types
-
A Tour of Go — Structs — Introdução interativa ao tipo struct. https://go.dev/tour/moretypes/2
-
Go by Example: Structs — Exemplos práticos comentados. https://gobyexample.com/structs
-
Effective Go — Embedding — Discussão oficial sobre composição por embedding. https://go.dev/doc/effective_go#embedding
-
Go Blog: JSON and Go — Como tags de struct e o pacote encoding/json trabalham juntos. https://go.dev/blog/json
-
Documentação do pacote encoding/json — Referência completa de serialização e desserialização JSON. https://pkg.go.dev/encoding/json