Menu

Structs em Golang: definir, inicializar, comparar e usar tags

Structs agrupam campos nomeados em um único tipo. Veja como defini-las e inicializá-las, valores zero, ponteiros para structs, structs anônimas, comparação, struct tags e a convenção de construtor NewX.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

Definindo e criando uma struct

Um tipo struct é uma lista de campos nomeados. Você o declara com type Nome struct { ... } e cria valores com um literal composto.

Saída:

Ana 31
{Name:Ana Email:ana@example.com Age:32 Admin:false}
{Name: Email: Age:0 Admin:false}
main.User{Name:"Bob", Email:"", Age:0, Admin:false}

%+v imprime os nomes dos campos e %#v imprime sintaxe Go. Os dois são o jeito mais rápido de inspecionar uma struct durante a depuração.

Campos do mesmo tipo podem ficar na mesma linha: X, Y float64. Nomes de campo com inicial maiúscula são exportados (visíveis para outros pacotes e para o encoding/json); os com minúscula não são.

Formas de literal

User{Name: "Ana", Age: 31}        // named fields: order-free, omitted fields are zero
User{"Ana", "a@x.com", 31, false} // positional: every field, in order
&User{Name: "Ana"}                // pointer to a new struct
new(User)                         // pointer to a zeroed struct

Prefira campos nomeados. Literais posicionais quebram quando alguém acrescenta ou reordena um campo, e o go vet avisa sobre eles em tipos struct de outros pacotes (por exemplo net/url.Error struct literal uses unkeyed fields). A forma posicional não tem problema em tipos locais minúsculos como Point{1, 2}.

Sem valores padrão: valores zero e construtores

Go não tem valores padrão para campos nem construtores na linguagem. Todo campo começa com o valor zero do seu tipo: 0, "", false, nil. Dois idiomas preenchem essa lacuna.

Torne o valor zero útil. sync.Mutex, bytes.Buffer e strings.Builder funcionam sem inicialização. Quando você puder projetar um tipo assim, faça.

Escreva uma função NewX quando um campo precisar de um padrão diferente de zero ou quando algo precisar ser alocado, como um map:

NewServer devolve um ponteiro porque o servidor tem métodos que o modificam e vai ser compartilhado. Devolver a struct por valor também é comum em tipos pequenos e imutáveis (time.Date devolve um time.Time).

Ponteiros para structs

Go desreferencia ponteiros de struct automaticamente no acesso a campos. Com p := &User{}, você escreve p.Name, e não (*p).Name.

Saída:

{11 12}
{11 12} {0 12}
{11 0}

Structs são valores. Atribuições e argumentos de função as copiam; um ponteiro compartilha uma só. Cópias campo a campo são rasas: um campo slice ou map na cópia ainda aponta para os mesmos dados. Veja ponteiros para saber quando usar cada um.

Structs anônimas

Um tipo struct pode ser escrito em linha, sem nome. Isso é útil para agrupamentos avulsos, testes orientados a tabela e para decodificar um JSON que você só lê uma vez.

A tabela []struct{ in string; want int } é o formato padrão de um teste em Go.

Comparando structs

Structs são comparáveis com == quando todos os campos são comparáveis. Igualdade significa todos os campos iguais. Structs comparáveis também podem ser chaves de map.

Para uma struct com campos slice ou map, escreva um método Equal ou compare nos testes com reflect.DeepEqual.

Struct tags

Uma tag é um literal de string depois do tipo de um campo. A linguagem a ignora; bibliotecas a leem com reflection. O leitor mais comum é o encoding/json:

Isto imprime {"id":7,"name":"Mug"}: a tag renomeia os campos, omitempty descarta o Price zero e o campo não exportado fica invisível para o encoder. As tags seguem a convenção key:"value", com várias chaves separadas por espaço: `json:"name" db:"user_name"`. Uma tag malformada compila sem erro e é ignorada em silêncio, o que o go vet detecta. A página de json cobre as opções.

Structs vazias e ordem dos campos

struct{} não tem campos e ocupa zero bytes. Ela é usada como valor de set (map[string]struct{}) e como sinal em channels (chan struct{}), em que só o evento importa.

A ordem dos campos afeta o layout na memória: o compilador preenche os campos até o alinhamento deles. Uma struct de bool, int64, bool ocupa 24 bytes em sistemas de 64 bits, enquanto int64, bool, bool ocupa 16. Isso só importa para tipos guardados em grande quantidade.

Erros comuns

  • Alterar uma cópia. for _, u := range users { u.Age++ } altera uma cópia a cada vez. Use o índice: users[i].Age++.
  • Esquecer de inicializar um campo map. O valor zero da struct guarda um map nil; escrever nele causa panic. Aloque em um construtor.
  • Copiar uma struct que contém um sync.Mutex. A cópia tem o seu próprio estado de lock. Passe essas structs por ponteiro; o go vet reporta a cópia.
  • Literais posicionais para tipos de outros pacotes. Eles quebram quando um campo é acrescentado.

Perguntas frequentes

Como inicializar uma struct em Go?

Com um literal composto. Campos nomeados são a forma usual: u := User{Name: "Ana", Age: 31}, e todo campo omitido recebe o seu valor zero. User{} dá todos os valores zero, &User{...} dá um ponteiro e new(User) dá um ponteiro para uma struct zerada.

Go tem valores padrão para campos de struct?

Não. Todo campo começa com o valor zero do seu tipo. O idioma é uma função construtora, func NewServer() *Server { return &Server{Port: 8080} }, ou projetar o tipo para que o seu valor zero já seja útil, como são sync.Mutex e bytes.Buffer.

Como comparar duas structs em Go?

Com ==, se todos os campos forem comparáveis. Dois valores struct são iguais quando todos os campos correspondentes são iguais. Uma struct com um campo slice, map ou função não pode ser comparada com == (erro de compilação); compare os campos você mesmo ou use reflect.DeepEqual nos testes.

O que são struct tags em Go?

Literais de string depois do tipo de um campo, como `json:"name,omitempty"`. O compilador os ignora; pacotes como encoding/json, drivers de banco de dados e validadores os leem com reflection para controlar como os campos são codificados, nomeados ou validados.

Como imprimir uma struct com os nomes dos campos em Go?

Use fmt.Printf("%+v\n", s), que imprime {Name:Ana Age:31}. %v imprime só os valores, e %#v imprime sintaxe Go incluindo o nome do tipo: main.User{Name:"Ana", Age:31}.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR