Structs: modelando dados sem classes
1. Introdução às Structs em Go
Go não possui classes. Essa afirmação pode soar estranha para quem vem de linguagens como Java, Python ou C#, mas é uma escolha deliberada de design. Em vez do paradigma clássico de orientação a objetos com herança, polimorfismo por subclasse e encapsulamento rígido, Go oferece structs — agregados de dados que podem ser combinados com métodos para criar comportamentos.
Uma struct em Go é um tipo composto que agrupa zero ou mais campos nomeados, cada um com seu próprio tipo. Enquanto classes definem tanto estado quanto comportamento em uma unidade monolítica, structs são fundamentalmente estruturas de dados que podem opcionalmente ter métodos associados. Essa distinção é sutil, mas poderosa: structs incentivam a separação entre dados e comportamento, promovendo composição sobre herança.
type Pessoa struct {
Nome string
Idade int
Email string
}
2. Declaração e Inicialização de Structs
A declaração de uma struct segue a sintaxe type Nome struct { campos }. Existem várias formas de inicializar uma struct:
// Declaração
type Endereco struct {
Rua string
Numero int
Cidade string
}
// Inicialização literal nomeada (recomendada)
e1 := Endereco{
Rua: "Av. Paulista",
Numero: 1000,
Cidade: "São Paulo",
}
// Inicialização literal posicional (frágil, não recomendada)
e2 := Endereco{"Rua Augusta", 500, "São Paulo"}
// Inicialização com var (todos os campos com zero values)
var e3 Endereco
// Usando new() retorna um ponteiro para a struct com zero values
e4 := new(Endereco)
// Equivalente ao new, mas mais idiomático
e5 := &Endereco{}
A inicialização nomeada é preferível por ser explícita e resiliente a mudanças na ordem dos campos. Structs vazias (struct{}) são úteis como valores de sinalização ou em canais.
3. Acessando e Modificando Campos
O operador ponto (.) é usado para acessar e modificar campos:
p := Pessoa{Nome: "João", Idade: 30}
fmt.Println(p.Nome) // João
p.Idade = 31
Um aspecto crucial é que structs em Go são tipos valor. Atribuir uma struct a outra variável ou passá-la para uma função cria uma cópia completa:
func envelhecer(p Pessoa) {
p.Idade++
}
p1 := Pessoa{Nome: "Maria", Idade: 25}
envelhecer(p1)
fmt.Println(p1.Idade) // 25 — não foi modificada!
Para modificar a struct original, usamos ponteiros:
func envelhecer(p *Pessoa) {
p.Idade++ // Go permite acesso direto sem desreferência explícita
}
p2 := &Pessoa{Nome: "Maria", Idade: 25}
envelhecer(p2)
fmt.Println(p2.Idade) // 26
Go automaticamente desreferencia ponteiros para structs quando usamos o operador ponto, tornando o código mais limpo.
4. Structs Aninhadas e Composição
Structs podem conter outras structs como campos, criando hierarquias de dados:
type Usuario struct {
Nome string
Endereco Endereco // aninhamento explícito
}
u := Usuario{
Nome: "Carlos",
Endereco: Endereco{
Rua: "Rua das Flores",
Numero: 123,
Cidade: "Curitiba",
},
}
fmt.Println(u.Endereco.Cidade) // Curitiba
Go também suporta composição com campos anônimos (embedding), que promove campos e métodos da struct embutida:
type Animal struct {
Nome string
Som string
}
func (a Animal) EmitirSom() {
fmt.Println(a.Som)
}
type Cachorro struct {
Animal // campo anônimo (embedding)
Raca string
}
c := Cachorro{
Animal: Animal{Nome: "Rex", Som: "Au au"},
Raca: "Labrador",
}
// Campos promovidos
fmt.Println(c.Nome) // Rex (promovido de Animal)
c.EmitirSom() // Au au (método promovido)
Se houver conflito de nomes entre a struct externa e a embutida, o campo da struct externa tem precedência.
5. Métodos em Structs
Métodos são funções com um receiver especial que as associa a um tipo:
type Retangulo struct {
Largura, Altura float64
}
// Receiver por valor
func (r Retangulo) Area() float64 {
return r.Largura * r.Altura
}
// Receiver por ponteiro
func (r *Retangulo) Escalar(fator float64) {
r.Largura *= fator
r.Altura *= fator
}
Quando usar cada receiver:
- Receiver por valor: quando o método não modifica a struct e a struct é pequena (poucos campos)
- Receiver por ponteiro: quando o método precisa modificar a struct, ou a struct é grande (evita cópia desnecessária)
Um método especial é String(), que implementa a interface Stringer do pacote fmt:
func (p Pessoa) String() string {
return fmt.Sprintf("%s (%d anos)", p.Nome, p.Idade)
}
p := Pessoa{"Ana", 28}
fmt.Println(p) // Ana (28 anos)
6. Tags de Struct e Reflexão
Tags são metadados anexados aos campos de uma struct, usados principalmente para serialização e validação:
type Produto struct {
ID int `json:"id" validate:"required"`
Nome string `json:"nome" validate:"required,min=3"`
Preco float64 `json:"preco" validate:"gt=0"`
Ativo bool `json:"ativo"`
}
O pacote reflect permite ler essas tags em tempo de execução:
import "reflect"
func lerTags(s interface{}) {
t := reflect.TypeOf(s)
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fmt.Printf("Campo: %s, JSON: %s\n", field.Name, field.Tag.Get("json"))
}
}
lerTags(Produto{})
// Campo: ID, JSON: id
// Campo: Nome, JSON: nome
// Campo: Preco, JSON: preco
// Campo: Ativo, JSON: ativo
Bibliotecas como encoding/json e validator usam intensamente tags para configurar comportamento automático.
7. Structs vs. Outras Abordagens
Structs em Go substituem classes com algumas diferenças fundamentais:
- Sem herança: usa-se composição (embedding) em vez de herança
- Sem construtores: o padrão é usar funções construtoras (
NewStruct()) - Zero values: toda struct declarada tem valores padrão para seus campos
Padrão construtor idiomático:
type Config struct {
Host string
Port int
Debug bool
}
func NewConfig(host string, port int) *Config {
if host == "" {
host = "localhost"
}
if port == 0 {
port = 8080
}
return &Config{
Host: host,
Port: port,
Debug: false,
}
}
Quando usar structs vs. outras estruturas:
- Structs: dados com campos nomeados e tipos fixos (modelagem de domínio)
- Maps: dados dinâmicos ou quando as chaves são desconhecidas em tempo de compilação
- Slices: coleções homogêneas de elementos
Structs oferecem segurança de tipo e desempenho superior, enquanto maps fornecem flexibilidade. A escolha depende do contexto e dos requisitos de cada aplicação.
Referências
- Documentação oficial: Structs — Tour interativo de Go explicando structs com exemplos práticos
- Effective Go: Composite Types — Seção do Effective Go sobre literais compostos e structs
- Go by Example: Structs — Exemplos concisos de declaração, inicialização e uso de structs
- Understanding Go's Empty Struct — Artigo de Dave Cheney sobre usos avançados de structs vazias
- JSON and Go — Post oficial do blog Go sobre serialização JSON com tags de struct
- Go Structs and Methods — Tutorial prático de Alex Edwards sobre structs e métodos em Go
- The Go Playground — Ambiente online para testar exemplos de structs e outros conceitos Go