Em Go, structs são tipos de dados compostos que permitem agrupar campos de diferentes tipos em uma única unidade lógica. Elas são a principal forma de criar estruturas de dados personalizadas, semelhantes a classes em outras linguagens, mas sem herança. Structs são amplamente utilizadas para modelar entidades do mundo real, como um cliente, um produto ou uma transação.

Nesta aula, abordaremos a definição de structs, seus campos, formas de inicialização e o uso de structs anônimas. Ao final, você será capaz de criar e utilizar structs de maneira eficiente em seus programas Go.

Definição

Uma struct é definida usando a palavra-chave type seguida do nome da struct e da palavra-chave struct. A definição inclui uma lista de campos entre chaves, onde cada campo possui um nome e um tipo. A sintaxe básica é:

type NomeStruct struct {
    campo1 tipo1
    campo2 tipo2
    // ...
}

Por exemplo, para representar um ponto no plano cartesiano, podemos definir:

type Ponto struct {
    X float64
    Y float64
}

Uma vez definida, a struct pode ser usada para criar variáveis do tipo Ponto. A definição da struct em si não aloca memória; ela apenas declara um novo tipo. A alocação ocorre quando criamos valores ou variáveis desse tipo.

Campos

Os campos de uma struct são acessados usando a notação de ponto (.). Cada campo pode ser de qualquer tipo, incluindo outros structs, slices, maps, etc. Os campos podem ser exportados (públicos) se iniciarem com letra maiúscula, ou não exportados (privados) se iniciarem com letra minúscula. Isso controla a visibilidade fora do pacote.

Exemplo de acesso a campos:

var p Ponto
p.X = 10.5
p.Y = 20.3
fmt.Println(p.X, p.Y) // 10.5 20.3

Structs podem conter campos de tipos complexos, como slices e maps. Por exemplo:

type Aluno struct {
    Nome   string
    Idade  int
    Notas  []float64
    Tags   map[string]string
}

É importante notar que os campos de uma struct são valores, não referências. Quando você atribui uma struct a outra variável, todos os campos são copiados. Para compartilhar a mesma struct, use ponteiros.

Inicialização

Existem várias maneiras de inicializar uma struct em Go. A forma mais comum é usar um literal de struct, que pode ser feito de duas maneiras: com nomes dos campos ou sem nomes (ordem dos campos).

Literal com nomes dos campos

p := Ponto{X: 10.0, Y: 20.0}

Você pode omitir campos que devem permanecer com valor zero. Por exemplo:

p := Ponto{X: 10.0} // Y será 0.0

Literal sem nomes (ordem dos campos)

p := Ponto{10.0, 20.0}

Nesse caso, os valores devem ser fornecidos na mesma ordem da declaração dos campos. Essa forma é menos robusta, pois se a ordem dos campos mudar, o código quebra.

Também é possível inicializar uma struct usando a função new ou com & para obter um ponteiro:

p := new(Ponto) // retorna *Ponto com todos campos zero
p.X = 5

p2 := &Ponto{X: 1, Y: 2} // ponteiro para struct

Se você não inicializar os campos, eles assumem o valor zero do tipo (0 para numéricos, false para booleanos, "" para strings, nil para slices/maps/ponteiros).

Structs anônimas

Structs anônimas são structs sem um nome definido explicitamente. Elas são úteis para criar estruturas temporárias, como em testes ou para agrupar dados em uma função sem precisar declarar um tipo global. A sintaxe é semelhante à definição de struct, mas sem a palavra-chave type e sem nome.

Exemplo:

func main() {
    pessoa := struct {
        Nome  string
        Idade int
    }{
        Nome:  "Alice",
        Idade: 30,
    }
    fmt.Println(pessoa)
}

Structs anônimas são frequentemente usadas em conjunto com JSON, por exemplo, para decodificar dados sem criar um tipo nomeado. Elas também são úteis em funções que retornam múltiplos valores heterogêneos, embora isso possa ser feito com tuplas implícitas.

Uma limitação é que structs anônimas não podem ser reutilizadas em outros lugares, pois não têm nome. Para reutilização, prefira definir um tipo nomeado.

Boas práticas

  • Use nomes significativos para os campos e para a struct.
  • Prefira inicialização com nomes dos campos para evitar erros de ordem.
  • Evite structs muito grandes; considere separar em structs menores.
  • Use ponteiros para structs grandes para evitar cópias desnecessárias.
  • Structs anônimas são úteis para uso local, mas não abuse.

Referências

Exercícios

  1. Crie uma struct chamada Livro com campos Titulo (string), Autor (string) e Ano (int). Inicialize um valor dessa struct usando um literal com nomes e imprima-o.

    ✓ Resposta:
    type Livro struct {
        Titulo string
        Autor  string
        Ano    int
    }
    
    func main() {
        livro := Livro{Titulo: "1984", Autor: "George Orwell", Ano: 1949}
        fmt.Println(livro)
    }
  2. Defina uma struct Retangulo com campos Largura e Altura (float64). Escreva uma função que receba um Retangulo e retorne sua área. Teste com um retângulo de largura 10 e altura 5.

    ✓ Resposta:
    type Retangulo struct {
        Largura float64
        Altura  float64
    }
    
    func area(r Retangulo) float64 {
        return r.Largura * r.Altura
    }
    
    func main() {
        r := Retangulo{Largura: 10, Altura: 5}
        fmt.Println(area(r)) // 50
    }
  3. Crie uma struct anônima para representar um carro com campos Marca e Modelo. Inicialize-a com valores e imprima.

    ✓ Resposta:
    func main() {
        carro := struct {
            Marca  string
            Modelo string
        }{
            Marca:  "Fiat",
            Modelo: "Uno",
        }
        fmt.Println(carro)
    }
  4. Dada a struct Pessoa com campos Nome e Idade, escreva um código que crie um ponteiro para uma struct Pessoa, modifique a idade e imprima o valor original e o modificado.

    ✓ Resposta:
    type Pessoa struct {
        Nome  string
        Idade int
    }
    
    func main() {
        p := &Pessoa{Nome: "João", Idade: 25}
        fmt.Println("Antes:", *p)
        p.Idade = 30
        fmt.Println("Depois:", *p)
    }
  5. Crie uma struct Time com campos Nome (string) e Jogadores (slice de strings). Inicialize com um time de futebol com 3 jogadores e depois adicione um quarto jogador usando append. Imprima o time final.

    ✓ Resposta:
    type Time struct {
        Nome      string
        Jogadores []string
    }
    
    func main() {
        t := Time{
            Nome:      "Flamengo",
            Jogadores: []string{"Gabigol", "Arrascaeta", "Bruno Henrique"},
        }
        t.Jogadores = append(t.Jogadores, "Everton Ribeiro")
        fmt.Println(t)
    }