<h2>Structs em Go: Definição, Embedding e Métodos</h2>
<h3>O que é uma Struct e Por Que Usar</h3>
<p>Uma struct em Go é um tipo de dado composto que agrupa múltiplos campos de tipos diferentes em uma única entidade. Diferentemente de linguagens orientadas a objetos clássicas, Go não possui classes, mas structs preenchem esse papel de forma elegante e eficiente. Você utiliza structs quando precisa representar conceitos do mundo real — um usuário, um produto, uma transação bancária — donde cada um desses conceitos possui características (campos) que devem ser agrupadas logicamente.</p>
<p>A filosofia do Go é simplicidade. Structs refletem isso: não há herança complexa, não há construtores obrigatórios, não há getters e setters automáticos. Você define exatamente o que precisa, nada mais. Isso torna o código mais previsível e fácil de manter em projetos grandes.</p>
<h3>Definição e Inicialização de Structs</h3>
<h4>Declarando uma Struct</h4>
<p>A sintaxe básica é direta. Você usa a palavra-chave <code>type</code> seguida do nome e <code>struct</code>, listando os campos com seus tipos:</p>
<pre><code class="language-go">package main
import "fmt"
type Pessoa struct {
Nome string
Idade int
Email string
}
func main() {
// Inicialização usando campos nomeados (recomendado)
p1 := Pessoa{
Nome: "Alice",
Idade: 30,
Email: "alice@example.com",
}
fmt.Println(p1)
// Inicialização posicional (menos segura)
p2 := Pessoa{"Bob", 25, "bob@example.com"}
fmt.Println(p2)
// Inicialização parcial (campos não mencionados recebem valor zero)
p3 := Pessoa{Nome: "Charlie"}
fmt.Println(p3) // {Charlie 0 }
}</code></pre>
<p>Note que campos de struct em Go começam com letra maiúscula ou minúscula, determinando sua visibilidade. Maiúscula significa exportado (acessível fora do pacote), minúscula significa privado. No exemplo acima, <code>Pessoa</code> é exportada, assim como seus campos.</p>
<h4>Tipos Aninhados e Campos com Tags</h4>
<p>Structs também podem conter outras structs como campos. Além disso, você pode adicionar <em>tags</em> aos campos — metadados usados por bibliotecas externas como <code>encoding/json</code>:</p>
<pre><code class="language-go">package main
import (
"encoding/json"
"fmt"
)
type Endereco struct {
Rua string json:"rua"
Cidade string json:"cidade"
CEP string json:"cep"
}
type Usuario struct {
ID int json:"id"
Nome string json:"nome"
Endereco Endereco json:"endereco"
}
func main() {
usuario := Usuario{
ID: 1,
Nome: "Diana",
Endereco: Endereco{
Rua: "Rua das Flores",
Cidade: "São Paulo",
CEP: "01310-100",
},
}
// Convertendo para JSON
jsonData, _ := json.MarshalIndent(usuario, "", " ")
fmt.Println(string(jsonData))
}</code></pre>
<p>As tags <code>json:"campo"</code> indicam qual chave JSON corresponde a cada campo da struct. Isso é especialmente útil ao trabalhar com APIs REST.</p>
<h3>Embedding: Composição sobre Herança</h3>
<h4>O Conceito de Embedding</h4>
<p>Go não suporta herança clássica. Em seu lugar, oferece <em>embedding</em> — uma forma elegante de composição onde você incorpora uma struct dentro de outra. O struct incorporado "promove" seus campos e métodos para a struct que o contém, criando uma relação de "é um" de forma composicional.</p>
<p>Imagine que você tem uma struct <code>Animal</code> com comportamentos comuns. Ao invés de criar <code>Cachorro extends Animal</code>, você incorpora <code>Animal</code> dentro de <code>Cachorro</code>. Assim, qualquer campo ou método de <code>Animal</code> é acessível diretamente através de <code>Cachorro</code>, sem necessidade de prefixo.</p>
<h4>Exemplo Prático de Embedding</h4>
<pre><code class="language-go">package main
import "fmt"
type Animal struct {
Nome string
Idade int
}
func (a Animal) Fazer_Som() string {
return "Som genérico"
}
// Cachorro incorpora Animal
type Cachorro struct {
Animal // Embedding: sem nome de campo
Raca string
}
// Gato também incorpora Animal
type Gato struct {
Animal
Cor string
}
// Métodos específicos que sobrescrevem o método do Animal
func (c Cachorro) Fazer_Som() string {
return "Au au!"
}
func (g Gato) Fazer_Som() string {
return "Miau!"
}
func main() {
cachorro := Cachorro{
Animal: Animal{Nome: "Rex", Idade: 5},
Raca: "Labrador",
}
gato := Gato{
Animal: Animal{Nome: "Whiskers", Idade: 3},
Cor: "Laranja",
}
// Acessando campos promovidos
fmt.Println(cachorro.Nome) // Rex
fmt.Println(gato.Idade) // 3
// Métodos sobrescritos (polimorfismo)
fmt.Println(cachorro.Fazer_Som()) // Au au!
fmt.Println(gato.Fazer_Som()) // Miau!
// Acessando método original via struct incorporado
fmt.Println(Animal{Nome: "Desconhecido"}.Fazer_Som()) // Som genérico
}</code></pre>
<p>Neste exemplo, <code>Cachorro</code> e <code>Gato</code> herdam os campos <code>Nome</code> e <code>Idade</code> de <code>Animal</code> através do embedding. Você pode acessá-los diretamente sem escrever <code>cachorro.Animal.Nome</code>. Além disso, cada tipo pode implementar sua própria versão do método <code>Fazer_Som()</code>, conseguindo polimorfismo sem herança.</p>
<h4>Embedding Múltiplo</h4>
<p>Go permite que uma struct incorpore mais de uma outra struct. Se houver conflito de nomes, você precisará ser explícito:</p>
<pre><code class="language-go">package main
import "fmt"
type Terrestre struct {
Velocidade int
}
type Aquatico struct {
Profundidade int
}
type Pato struct {
Terrestre
Aquatico
Nome string
}
func main() {
pato := Pato{
Terrestre: Terrestre{Velocidade: 40},
Aquatico: Aquatico{Profundidade: 2},
Nome: "Donald",
}
fmt.Println(pato.Velocidade) // 40
fmt.Println(pato.Profundidade) // 2
fmt.Println(pato.Nome) // Donald
}</code></pre>
<h3>Métodos em Structs</h3>
<h4>Receptores e a Sintaxe de Métodos</h4>
<p>Diferentemente de outras linguagens, Go não vincula métodos a structs através de definição de classe. Em seu lugar, você declara funções com um <em>receptor</em> — um parâmetro especial que vem antes do nome da função. O receptor estabelece a ligação entre a função e o tipo.</p>
<pre><code class="language-go">package main
import "fmt"
import "math"
type Circulo struct {
Raio float64
}
// Método com receptor por valor
func (c Circulo) Area() float64 {
return math.Pi c.Raio c.Raio
}
// Método com receptor por ponteiro
func (c *Circulo) Crescer(percentual float64) {
c.Raio = c.Raio * (1 + percentual/100)
}
func main() {
circulo := Circulo{Raio: 5}
fmt.Printf("Área inicial: %.2f\n", circulo.Area()) // 78.54
circulo.Crescer(50) // Cresce 50%
fmt.Printf("Área após crescimento: %.2f\n", circulo.Area()) // 176.71
}</code></pre>
<p><strong>Receptor por valor vs. receptor por ponteiro</strong>: quando você usa <code>(c Circulo)</code>, o método recebe uma cópia da struct. Modificações dentro do método não afetam o original. Quando usa <code>(c *Circulo)</code>, o método recebe um ponteiro e pode modificar a struct original. Use receptor por valor para métodos que apenas leem dados, e receptor por ponteiro para métodos que modificam estado.</p>
<h4>Interfaces e Métodos</h4>
<p>Structs em Go implementam interfaces implicitamente. Uma interface define um conjunto de métodos. Qualquer struct que implemente todos esses métodos satisfaz automaticamente a interface, sem necessidade de declaração explícita:</p>
<pre><code class="language-go">package main
import "fmt"
type Veiculo interface {
Velocidade_maxima() float64
Descricao() string
}
type Carro struct {
Marca string
Modelo string
}
type Bicicleta struct {
Tipo string
}
func (c Carro) Velocidade_maxima() float64 {
return 250.0
}
func (c Carro) Descricao() string {
return fmt.Sprintf("%s %s", c.Marca, c.Modelo)
}
func (b Bicicleta) Velocidade_maxima() float64 {
return 40.0
}
func (b Bicicleta) Descricao() string {
return fmt.Sprintf("Bicicleta %s", b.Tipo)
}
func ExibirVeiculo(v Veiculo) {
fmt.Printf("%s com velocidade máxima de %.0f km/h\n",
v.Descricao(),
v.Velocidade_maxima())
}
func main() {
carro := Carro{Marca: "Toyota", Modelo: "Corolla"}
bike := Bicicleta{Tipo: "Mountain Bike"}
ExibirVeiculo(carro) // Funciona
ExibirVeiculo(bike) // Funciona também
}</code></pre>
<p>Aqui, <code>Carro</code> e <code>Bicicleta</code> implementam todos os métodos de <code>Veiculo</code>, portanto ambas satisfazem a interface. A função <code>ExibirVeiculo</code> aceita qualquer <code>Veiculo</code>, demonstrando polimorfismo em Go.</p>
<h4>Método String Customizado</h4>
<p>Go oferece uma convenção especial: se você implementar o método <code>String()</code> em uma struct, ele será automaticamente chamado quando você tentar converter a struct para string (por exemplo, ao usar <code>fmt.Println</code>):</p>
<pre><code class="language-go">package main
import "fmt"
type Produto struct {
Nome string
Preco float64
Estoque int
}
// Implementando String para customizar a representação
func (p Produto) String() string {
return fmt.Sprintf("Produto: %s | R$ %.2f | Estoque: %d",
p.Nome,
p.Preco,
p.Estoque)
}
func main() {
produto := Produto{
Nome: "Notebook",
Preco: 3500.00,
Estoque: 15,
}
fmt.Println(produto)
// Output: Produto: Notebook | R$ 3500.00 | Estoque: 15
}</code></pre>
<h3>Boas Práticas e Padrões Comuns</h3>
<h4>Construtor Pattern</h4>
<p>Embora Go não force construtores, é comum criar funções que retornam uma instância inicializada corretamente. Essa é uma forma segura de garantir que a struct tenha valores sensatos:</p>
<pre><code class="language-go">package main
import (
"fmt"
"time"
)
type Pedido struct {
ID string
Cliente string
Itens []string
CriadoEm time.Time
Completo bool
}
// Função construtora (convenção: começar com "New")
func NewPedido(cliente string) *Pedido {
return &Pedido{
ID: fmt.Sprintf("PED-%d", time.Now().UnixNano()),
Cliente: cliente,
Itens: make([]string, 0),
CriadoEm: time.Now(),
Completo: false,
}
}
func (p *Pedido) AdicionarItem(item string) {
p.Itens = append(p.Itens, item)
}
func (p *Pedido) Finalizar() {
p.Completo = true
}
func main() {
pedido := NewPedido("João Silva")
pedido.AdicionarItem("Livro")
pedido.AdicionarItem("Caneta")
pedido.Finalizar()
fmt.Printf("Pedido %s para %s: %v\n",
pedido.ID,
pedido.Cliente,
pedido.Itens)
}</code></pre>
<h4>Campos Privados e Métodos de Acesso</h4>
<p>Campos privados (iniciados com letra minúscula) frequentemente são acessados através de métodos getter/setter, especialmente em structs que precisam validar dados:</p>
<pre><code class="language-go">package main
import (
"fmt"
)
type ContaBancaria struct {
titular string
saldo float64
}
// Getter
func (c *ContaBancaria) ObterSaldo() float64 {
return c.saldo
}
// Getter
func (c *ContaBancaria) ObterTitular() string {
return c.titular
}
// Setter com validação
func (c *ContaBancaria) Depositar(valor float64) error {
if valor <= 0 {
return fmt.Errorf("valor deve ser positivo")
}
c.saldo += valor
return nil
}
func main() {
conta := ContaBancaria{
titular: "Maria",
saldo: 1000.00,
}
fmt.Printf("Titular: %s, Saldo: R$ %.2f\n",
conta.ObterTitular(),
conta.ObterSaldo())
conta.Depositar(500)
fmt.Printf("Novo saldo: R$ %.2f\n", conta.ObterSaldo())
}</code></pre>
<h4>Evitar Embed Circular</h4>
<p>Uma armadilha comum é criar dois tipos que se incorporam mutuamente, causando um erro de compilação. Go não permite isso. Se você precisa de relacionamento complexo, use campos nomeados ao invés de embedding:</p>
<pre><code class="language-go"></code></pre>
<h2>Referências</h2>
<ol>
<li><a href="https://golang.org/ref/spec#Struct_types" target="_blank" rel="noopener noreferrer">Go Official Documentation - Structs</a> — Documentação oficial sobre tipos struct em Go.</li>
</ol>
<ol>
<li><a href="https://golang.org/doc/effective_go#methods" target="_blank" rel="noopener noreferrer">Effective Go - Methods</a> — Guia oficial sobre como escrever métodos idiomáticos em Go.</li>
</ol>
<ol>
<li><a href="https://gobyexample.com/structs" target="_blank" rel="noopener noreferrer">Go by Example - Structs</a> — Tutorial prático com exemplos interativos sobre structs.</li>
</ol>
<ol>
<li><a href="https://www.gopl.io/" target="_blank" rel="noopener noreferrer">Alan Donovan & Brian Kernighan - The Go Programming Language</a> — Livro clássico, capítulo 4 trata structs, métodos e interfaces em profundidade.</li>
</ol>
<ol>
<li><a href="https://dave.cheney.net/2015/05/22/embedding-is-not-inheritance" target="_blank" rel="noopener noreferrer">Dave Cheney - Embedding in Go</a> — Artigo fundamental explicando embedding e por que não é herança.</li>
</ol>
<p><!-- FIM --></p>