Pular para o conteúdo
•Leitura

Structs em Go: organizando dados de forma clara

Structs em Go: organizando dados de forma clara

Para quem já estudou classes em Java, uma struct pode parecer familiar. Porém, Go não utiliza classes da mesma forma: em vez disso, combina structs, funções e métodos para criar estruturas organizadas e reutilizáveis.

O que é uma struct

Uma struct é um tipo composto formado por campos. Cada campo possui um nome e um tipo:

type Usuario struct {     
  Nome  string     
  Email string     
  Idade int 
}

Nesse exemplo, Usuario possui três campos: Nome, Email e Idade.

Depois de criar o tipo, podemos instanciá-lo:

usuario := Usuario {
     Nome:  "Samuel",     
     Email: "samuel@example.com",     
     Idade: 16, 
}  

fmt.Println(usuario.Nome)

Usar os nomes dos campos deixa a criação mais clara e reduz o risco de preencher os valores na ordem errada.

Criando structs

Existem várias formas de criar uma struct:

// Literal nomeado 
u1 := Usuario{     
  Nome:  "Ana",     
  Email: "ana@example.com",     
  Idade: 18, }  
// Literal posicional 
u2 := Usuario{"Carlos", "carlos@example.com", 20}  
// Struct com valores padrão 
var u3 Usuario

A forma nomeada costuma ser a mais recomendada, porque continua legível mesmo quando a struct possui muitos campos.

Quando uma struct é criada sem valores, seus campos recebem os valores zero de seus tipos:

var usuario Usuario  
fmt.Println(usuario.Nome)  // ""
fmt.Println(usuario.Idade) // 0

Acessando e alterando campos

Os campos são acessados usando o operador .:

usuario := Usuario {     
  Nome:  "João",     
  Email: "joao@example.com",     
  Idade: 21, 
}  

usuario.Idade = 22 
usuario.Email = "novo@example.com"  
fmt.Println(usuario)

Também é possível acessar os campos por meio de um ponteiro para a struct. Go permite usar a mesma notação:

usuario := &Usuario{     
  Nome:  "Maria",     
  Email: "maria@example.com",     
  Idade: 25, 
}  

usuario.Idade++ 
fmt.Println(usuario.Idade)

O compilador faz automaticamente a desreferenciação nesse caso.

Structs e funções

Structs podem ser passadas para funções como qualquer outro valor:

func exibirUsuario(usuario Usuario) {     
  fmt.Printf("%s - %s\n", usuario.Nome, usuario.Email) 
}

Nesse caso, a struct é copiada. Se a estrutura for grande ou precisar ser modificada, é comum usar um ponteiro:


func aumentarIdade(usuario *Usuario) {
     usuario.Idade++ 
}  
func main() {     
  usuario := Usuario{         
    Nome:  "Lucas",         
    Idade: 17,     
  }      
  aumentarIdade(&usuario)     
  fmt.Println(usuario.Idade) 
}

Usar um ponteiro permite alterar a struct original sem criar uma cópia completa.

Métodos em structs

Go permite associar métodos a structs. Um método é uma função que possui um receptor:

type Usuario struct {     
  Nome  string
  Idade int
}

func (u Usuario) Apresentar() string {
  return fmt.Sprintf("Olá, meu nome é %s", u.Nome) 
}

Uso:

usuario := Usuario{
     Nome:  "Samuel",
     Idade: 16,
}  

fmt.Println(usuario.Apresentar())

Quando o método não precisa alterar a struct, um receptor por valor pode ser suficiente.

Para modificar os dados originais, use um receptor ponteiro:

func (u *Usuario) FazerAniversario() {
     u.Idade++ 
}

Agora:

usuario.FazerAniversario() 
fmt.Println(usuario.Idade)

Structs anônimas

Também é possível criar structs sem declarar um tipo com nome:

produto := struct {
     Nome  string
     Preco float64
 }{     
     Nome:  "Teclado",     
     Preco: 149.90,
}

Structs anônimas são úteis para dados temporários, testes ou respostas específicas de uma função.

Structs aninhadas

Uma struct pode conter outra struct como campo:

type Endereco struct {
     Rua    string
     Cidade string 
}  

type Usuario struct {
     Nome      string
     Email     string
     Endereco  Endereco
}

Uso:

usuario := Usuario{
     Nome: "Pedro",
     Endereco: Endereco{
         Rua:    "Rua Principal",
         Cidade: "São Paulo",
     }, 
}  
fmt.Println(usuario.Endereco.Cidade)

Essa abordagem ajuda a organizar modelos maiores sem deixar todos os campos misturados.

Tags em structs

Tags são metadados associados aos campos. Elas são muito usadas para converter structs para JSON, validar dados e mapear registros de banco de dados:

type Usuario struct {
     Nome  string json:"nome"
     Email string json:"email"
     Idade int    json:"idade" 
}

Com isso, o JSON gerado pode usar nomes em minúsculo:

usuario := Usuario{
     Nome:  "Ana",
     Email: "ana@example.com",
     Idade: 18, 
}  

dados, err := json.Marshal(usuario) 

if err != nil {
     log.Fatal(err)
}  

fmt.Println(string(dados))

Resultado:

{"nome":"Ana","email":"ana@example.com","idade":18}

Tags são especialmente importantes em APIs backend escritas em Go.

Exportação de campos

Em Go, um campo ou tipo começa com letra maiúscula quando é exportado e pode ser acessado por outros pacotes:

type Usuario struct {
     Nome  string // Exportado
     email string // Não exportado 
}

type Usuario struct { Nome string // Exportado email string // Não exportado }

O campo Nome pode ser acessado fora do pacote. Já email fica restrito ao pacote onde foi declarado.

Essa regra também se aplica ao próprio nome da struct e aos métodos.

Structs e interfaces

Structs podem implementar interfaces implicitamente. Não é necessário declarar algo como implements.

type Imprimivel interface {
     Imprimir() string
}  

type Produto struct {
     Nome  string
     Preco float64 
}  

func (p Produto) Imprimir() string {
     return fmt.Sprintf("%s - R$ %.2f", p.Nome, p.Preco) 
}

Como Produto possui o método exigido pela interface, ele pode ser usado como Imprimivel:

func mostrar(item Imprimivel) {
     fmt.Println(item.Imprimir()) 
}  

produto := Produto{
     Nome:  "Mouse",
    Preco: 89.90, 
}
  
mostrar(produto)

Comparação entre structs

Uma struct pode ser comparada com == quando todos os seus campos também forem comparáveis:

type Ponto struct {
     X int
     Y int 
}  
p1 := Ponto{X: 1, Y: 2} 
p2 := Ponto{X: 1, Y: 2}  
fmt.Println(p1 == p2) // true

Porém, structs que contêm slices, maps ou funções não podem ser comparadas diretamente com ==.

Exemplo em uma API

Structs são muito utilizadas para representar requisições e respostas HTTP:

type CriarJogoRequest struct {
     Nome        string `json:"nome"`
     Descricao   string `json:"descricao"`
     CategoriaID int    `json:"categoria_id"` 
}  

type JogoResponse struct {
     ID        int    `json:"id"`
     Nome      string `json:"nome"`
     Publicado bool   `json:"publicado"` 
}

Essa separação ajuda a evitar que o modelo do banco seja exposto diretamente pela API e deixa o código mais organizado.

Conclusão

Structs são fundamentais em Go porque permitem representar dados de maneira simples, explícita e eficiente. Elas podem ser combinadas com métodos, ponteiros, interfaces, tags e outras structs para construir aplicações completas.

No desenvolvimento backend, especialmente em APIs e sistemas que utilizam banco de dados, praticamente tudo pode ser modelado com structs: usuários, jogos, categorias, wallets, respostas, requisições e configurações.

Entender bem esse recurso é um passo importante para escrever código Go mais organizado e avançar na construção de projetos maiores.