Go

Como Usar Structs em Go: Definição, Embedding e Métodos em Produção

13 min de leitura

Como Usar Structs em Go: Definição, Embedding e Métodos em Produção

Structs em Go: Definição, Embedding e Métodos O que é uma Struct e Por Que Usar 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. 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. Definição e Inicialização de Structs Declarando uma Struct A sintaxe básica é direta. Você usa a palavra-chave seguida do nome e , listando os campos com

<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 &quot;fmt&quot;

type Pessoa struct {

Nome string

Idade int

Email string

}

func main() {

// Inicialização usando campos nomeados (recomendado)

p1 := Pessoa{

Nome: &quot;Alice&quot;,

Idade: 30,

Email: &quot;alice@example.com&quot;,

}

fmt.Println(p1)

// Inicialização posicional (menos segura)

p2 := Pessoa{&quot;Bob&quot;, 25, &quot;bob@example.com&quot;}

fmt.Println(p2)

// Inicialização parcial (campos não mencionados recebem valor zero)

p3 := Pessoa{Nome: &quot;Charlie&quot;}

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 (

&quot;encoding/json&quot;

&quot;fmt&quot;

)

type Endereco struct {

Rua string json:&quot;rua&quot;

Cidade string json:&quot;cidade&quot;

CEP string json:&quot;cep&quot;

}

type Usuario struct {

ID int json:&quot;id&quot;

Nome string json:&quot;nome&quot;

Endereco Endereco json:&quot;endereco&quot;

}

func main() {

usuario := Usuario{

ID: 1,

Nome: &quot;Diana&quot;,

Endereco: Endereco{

Rua: &quot;Rua das Flores&quot;,

Cidade: &quot;São Paulo&quot;,

CEP: &quot;01310-100&quot;,

},

}

// Convertendo para JSON

jsonData, _ := json.MarshalIndent(usuario, &quot;&quot;, &quot; &quot;)

fmt.Println(string(jsonData))

}</code></pre>

<p>As tags <code>json:&quot;campo&quot;</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 &quot;promove&quot; seus campos e métodos para a struct que o contém, criando uma relação de &quot;é um&quot; 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 &quot;fmt&quot;

type Animal struct {

Nome string

Idade int

}

func (a Animal) Fazer_Som() string {

return &quot;Som genérico&quot;

}

// 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 &quot;Au au!&quot;

}

func (g Gato) Fazer_Som() string {

return &quot;Miau!&quot;

}

func main() {

cachorro := Cachorro{

Animal: Animal{Nome: &quot;Rex&quot;, Idade: 5},

Raca: &quot;Labrador&quot;,

}

gato := Gato{

Animal: Animal{Nome: &quot;Whiskers&quot;, Idade: 3},

Cor: &quot;Laranja&quot;,

}

// 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: &quot;Desconhecido&quot;}.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 &quot;fmt&quot;

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: &quot;Donald&quot;,

}

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 &quot;fmt&quot;

import &quot;math&quot;

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(&quot;Área inicial: %.2f\n&quot;, circulo.Area()) // 78.54

circulo.Crescer(50) // Cresce 50%

fmt.Printf(&quot;Área após crescimento: %.2f\n&quot;, 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 &quot;fmt&quot;

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(&quot;%s %s&quot;, c.Marca, c.Modelo)

}

func (b Bicicleta) Velocidade_maxima() float64 {

return 40.0

}

func (b Bicicleta) Descricao() string {

return fmt.Sprintf(&quot;Bicicleta %s&quot;, b.Tipo)

}

func ExibirVeiculo(v Veiculo) {

fmt.Printf(&quot;%s com velocidade máxima de %.0f km/h\n&quot;,

v.Descricao(),

v.Velocidade_maxima())

}

func main() {

carro := Carro{Marca: &quot;Toyota&quot;, Modelo: &quot;Corolla&quot;}

bike := Bicicleta{Tipo: &quot;Mountain Bike&quot;}

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 &quot;fmt&quot;

type Produto struct {

Nome string

Preco float64

Estoque int

}

// Implementando String para customizar a representação

func (p Produto) String() string {

return fmt.Sprintf(&quot;Produto: %s | R$ %.2f | Estoque: %d&quot;,

p.Nome,

p.Preco,

p.Estoque)

}

func main() {

produto := Produto{

Nome: &quot;Notebook&quot;,

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 (

&quot;fmt&quot;

&quot;time&quot;

)

type Pedido struct {

ID string

Cliente string

Itens []string

CriadoEm time.Time

Completo bool

}

// Função construtora (convenção: começar com &quot;New&quot;)

func NewPedido(cliente string) *Pedido {

return &amp;Pedido{

ID: fmt.Sprintf(&quot;PED-%d&quot;, 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(&quot;João Silva&quot;)

pedido.AdicionarItem(&quot;Livro&quot;)

pedido.AdicionarItem(&quot;Caneta&quot;)

pedido.Finalizar()

fmt.Printf(&quot;Pedido %s para %s: %v\n&quot;,

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 (

&quot;fmt&quot;

)

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 &lt;= 0 {

return fmt.Errorf(&quot;valor deve ser positivo&quot;)

}

c.saldo += valor

return nil

}

func main() {

conta := ContaBancaria{

titular: &quot;Maria&quot;,

saldo: 1000.00,

}

fmt.Printf(&quot;Titular: %s, Saldo: R$ %.2f\n&quot;,

conta.ObterTitular(),

conta.ObterSaldo())

conta.Depositar(500)

fmt.Printf(&quot;Novo saldo: R$ %.2f\n&quot;, 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 &amp; 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>&lt;!-- FIM --&gt;</p>

Comentários

Mais em Go

Guia Completo de Testes de Integração em Go: Banco Real com testcontainers-go
Guia Completo de Testes de Integração em Go: Banco Real com testcontainers-go

Por que Testes de Integração Importam em Go Testes de integração são diferent...

Profiling de Memória em Go: pprof, Heap Dumps e Otimizações: Do Básico ao Avançado
Profiling de Memória em Go: pprof, Heap Dumps e Otimizações: Do Básico ao Avançado

Introdução ao Profiling de Memória em Go Profiling é a análise sistemática de...

O que Todo Dev Deve Saber sobre Mocks em Go: Interfaces, testify/mock e mockery
O que Todo Dev Deve Saber sobre Mocks em Go: Interfaces, testify/mock e mockery

Por que Testamos com Mocks? Testes unitários são o alicerce de um código conf...