Structs
Uma struct é uma estrutura usada para agrupar dados relacionados em um único tipo. Até agora, quando queríamos guardar dados de uma pessoa, poderíamos fazer assim:
nome := "Ana"
idade := 20
email := "ana@email.com"
Isso funciona, mas os valores ficam soltos. Uma struct permite juntar esses dados em uma única estrutura:
type Usuario struct {
Nome string
Idade int
Email string
}
Agora Usuario representa um tipo próprio (o tipo é Usuario), com três campos.
Por que structs existem
As Structs existem para representar dados com formato conhecido. Por exemplo, um usuário tem nome, idade e email. Um produto tem nome, preço e estoque. Um pedido tem código, valor e status.
Até poderíamos usar map[string]any para isso:
usuario := map[string]any{
"nome": "Ana",
"idade": 20,
"email": "ana@email.com",
}
Mas isso perde segurança de tipo, o Go não sabe com clareza quais campos existem e quais tipos cada campo deveria ter. Com struct, o formato fica explícito:
type Usuario struct {
Nome string
Idade int
Email string
}
Agora o Go sabe que Nome é string, Idade é int e Email é string.
Criando uma struct
Primeiro declaramos o tipo:
type Usuario struct {
Nome string
Idade int
Email string
}
Exemplo completo:
package main
import "fmt"
// Usuario representa os dados de um usuário.
type Usuario struct {
Nome string
Idade int
Email string
}
func main() {
// Cria um valor do tipo Usuario.
usuario := Usuario{
Nome: "Ana",
Idade: 20,
Email: "ana@email.com",
}
fmt.Println(usuario)
}
Resultado:
{Ana 20 ana@email.com}
Struct declarada dentro e fora da função
O mais comum é declarar uma struct fora de função, quando ela representa algo importante no programa.
package main
import "fmt"
// Usuario é um tipo criado no nível do pacote.
// Pode ser usado por qualquer função dentro deste pacote.
type Usuario struct {
Nome string
Idade int
}
func main() {
usuario := Usuario{
Nome: "Ana",
Idade: 20,
}
fmt.Println(usuario)
}
Como ele foi declarado fora de main, outras funções do mesmo pacote também podem usar esse tipo.
func mostrarUsuario(usuario Usuario) {
fmt.Println(usuario.Nome)
}
Essa forma é melhor quando a struct será reutilizada. Mas também é possível declarar uma struct dentro de uma função.
package main
import "fmt"
func main() {
// Tipo Usuario criado apenas dentro da função main.
type Usuario struct {
Nome string
Idade int
}
usuario := Usuario{
Nome: "Ana",
Idade: 20,
}
fmt.Println(usuario)
}
Isso funciona, mas o tipo Usuario só existe dentro da função main, fora dela, esse tipo não pode ser usado.
Struct anônima dentro da função
Também podemos criar uma struct sem nome, direto na variável.
package main
import "fmt"
func main() {
usuario := struct {
Nome string
Idade int
}{
Nome: "Ana",
Idade: 20,
}
fmt.Println(usuario)
}
Isso é útil para casos pequenos e temporários, quando não vale a pena criar um tipo com nome.
Acessando campos da struct
Para acessar um campo, usamos ponto ..
usuario.Nome
usuario.Idade
usuario.Email
Exemplo:
package main
import "fmt"
type Usuario struct {
Nome string
Idade int
Email string
}
func main() {
usuario := Usuario{
Nome: "Ana",
Idade: 20,
Email: "ana@email.com",
}
// Acessa campos específicos da struct.
fmt.Println(usuario.Nome)
fmt.Println(usuario.Idade)
fmt.Println(usuario.Email)
}
Resultado:
Ana
20
ana@email.com
Alterando campos da struct
Campos de uma struct podem ser alterados usando =.
package main
import "fmt"
type Usuario struct {
Nome string
Idade int
Email string
}
func main() {
usuario := Usuario{
Nome: "Ana",
Idade: 20,
Email: "ana@email.com",
}
// Altera o campo Idade.
usuario.Idade = 21
fmt.Println(usuario.Idade)
}
Resultado:
21
A ideia é parecida com alterar uma variável, só que agora estamos alterando um campo dentro da struct.
Valor zero em structs
Se criarmos uma struct sem preencher os campos, cada campo recebe o valor zero do próprio tipo.
package main
import "fmt"
type Usuario struct {
Nome string
Idade int
Ativo bool
}
func main() {
var usuario Usuario
fmt.Println(usuario.Nome) // string vazia
fmt.Println(usuario.Idade) // 0
fmt.Println(usuario.Ativo) // false
}
Valores zero usados nesse exemplo:
string -> ""
int -> 0
bool -> false
Criando struct com alguns campos
Não é obrigatório preencher todos os campos ao criar a struct.
package main
import "fmt"
type Usuario struct {
Nome string
Idade int
Email string
}
func main() {
usuario := Usuario{
Nome: "Ana",
}
fmt.Println(usuario.Nome)
fmt.Println(usuario.Idade)
fmt.Println(usuario.Email)
}
Resultado:
Ana
0
A Idade ficou 0, o Email ficou com string vazia. Isso pode ser útil, mas também exige cuidado, às vezes um campo vazio pode indicar que faltou preencher algum dado importante.
Criando struct sem nome dos campos
O Go permite criar uma struct passando os valores na ordem dos campos.
usuario := Usuario{"Ana", 20, "ana@email.com"}
Isso funciona, mas normalmente é melhor evitar, sempre prefira informar o nome dos campos:
usuario := Usuario{
Nome: "Ana",
Idade: 20,
Email: "ana@email.com",
}
Com os nomes dos campos, o código fica mais claro e menos frágil. Se a struct mudar de ordem depois, isso pode dar problema.
Struct dentro de slice
É comum ter uma lista de structs.
package main
import "fmt"
type Usuario struct {
Nome string
Idade int
}
func main() {
usuarios := []Usuario{
{Nome: "Ana", Idade: 20},
{Nome: "Carlos", Idade: 25},
{Nome: "Bia", Idade: 18},
}
for _, usuario := range usuarios {
fmt.Println(usuario.Nome, usuario.Idade)
}
}
Resultado:
Ana 20
Carlos 25
Bia 18
Esse formato aparece muito em APIs, bancos de dados e listas de resultados.
Struct vs Map
Use struct quando os campos são conhecidos.
type Usuario struct {
Nome string
Idade int
}
Use map quando as chaves são mais dinâmicas.
config := map[string]string{
"ambiente": "dev",
"porta": "8080",
}
Um map[string]any até permite valores variados:
usuario := map[string]any{
"nome": "Ana",
"idade": 20,
}
Mas para dados com formato fixo, struct é melhor. Com struct, o Go consegue verificar os tipos antes do programa rodar.
Campos com letra maiúscula e minúscula
Em Go, o nome do campo também importa para visibilidade, campos com letra maiúscula podem ser acessados de outros pacotes.
type Usuario struct {
Nome string
}
Campos com letra minúscula ficam acessíveis apenas dentro do mesmo pacote.
type Usuario struct {
nome string
}
Como ainda estamos trabalhando em um único arquivo com package main, isso não pesa tanto agora. Mais tarde, quando estudarmos packages, essa diferença vai ficar mais importante.
Por enquanto, nos exemplos, vamos usar campos com letra maiúscula:
type Usuario struct {
Nome string
Idade int
}