DEV Community

Renato O da Silva
Renato O da Silva

Posted on

Go do zero: var vs :=

Go tem duas formas de declarar variáveis: var e :=. Elas existem por motivos diferentes e têm regras diferentes. Saber quando cada uma se aplica evitamos erros bobos e código que não compila.

Sintaxe

var (forma longa)

var x int           // tipo explícito, recebe o zero value
var x int = 5       // tipo e valor
var x = 5           // valor com tipo inferido
var x, y = 1, 2     // múltiplas variáveis de uma vez

var (
    a int
    b string = "hi"
    c = 3.14
)

Enter fullscreen mode Exit fullscreen mode

O bloco var (...) é útil quando você quer agrupar declarações relacionadas, especialmente em nível de pacote.

:= (short variable declaration)

x := 5
a, b := 1, "hello"
ch := make(chan int)
v, ok := m[key]

Enter fullscreen mode Exit fullscreen mode

Pense no := como um atalho para var x = expr. Ele sempre infere o tipo a partir do valor à direita, e por isso o valor é obrigatório.

Regras principais

Aspecto var :=
Onde usar Pacote ou função Só dentro de função
Tipo explícito Opcional Não tem, sempre infere
Inicializador Opcional (usa o zero value) Obrigatório
Redeclaração Não Sim, com condições

A diferença mais importante na prática: := não funciona fora de uma função. Se você tentar usar no escopo de pacote, o compilador reclama.

Redeclaração com :=

Esse é um dos pontos que mais confunde quem está começando. O := permite reusar nomes já existentes, mas só se algumas condições forem atendidas:

  1. As variáveis já existentes precisam ter sido declaradas no mesmo bloco.
  2. O tipo precisa ser o mesmo.
  3. Pelo menos uma variável do lado esquerdo precisa ser nova.
  4. Nomes não-blank precisam ser únicos no lado esquerdo.

Um padrão comum com tratamento de erro:

field, offset := nextField(s, 0)
field, offset = nextField(s, offset)   // = atribui (ambas já existem)
field2, offset := nextField(s, offset) // := redeclara offset (field2 é nova)
Enter fullscreen mode Exit fullscreen mode

Note que na segunda linha usamos =, porque tudo já existe. Na terceira voltamos para := porque field2 é nova, e isso "carrega" a redeclaração de offset junto.

Já o exemplo abaixo não compila:

x, y, x := 1, 2, 3   // ilegal: x aparece duas vezes
Enter fullscreen mode Exit fullscreen mode

Quando usar cada um

Não existe uma regra absoluta, mas existem situações em que a escolha é praticamente automática.

Use var quando:

Está em nível de pacote (não tem escolha):

package main

var version = "1.0"   // := não funciona aqui
Enter fullscreen mode Exit fullscreen mode

Quer o zero value explicitamente:

var users []string   // nil slice, sem alocar nada
Enter fullscreen mode Exit fullscreen mode

Quer um tipo diferente do que seria inferido:

var port int32 = 8080   // com := seria int
Enter fullscreen mode Exit fullscreen mode

Está declarando várias variáveis de tipos diferentes em bloco:

var (
    name   string
    age    int
    active bool
)
Enter fullscreen mode Exit fullscreen mode

Use := quando:

Está dentro de uma função e tem um valor para inicializar.

Recebendo o retorno de uma função (esse é o uso mais idiomático):

data, err := os.ReadFile("config.yaml")
Enter fullscreen mode Exit fullscreen mode

Em if, for ou switch com escopo local:

if v, ok := m["k"]; ok {
    // Faça algo
}
Enter fullscreen mode Exit fullscreen mode

Aqui v e ok só existem dentro do if. Esse padrão é muito comum em Go e vale a pena memorizar.

Múltipla atribuição

Go permite atribuir várias variáveis de uma vez, onde temos algumas coisas úteis:

a, b := 1, 2
a, b = b, a            // swap sem variável temporária
x, _ := getXY()        // descarta o segundo retorno com _
Enter fullscreen mode Exit fullscreen mode

O _ é o blank identifier. Use ele quando precisa receber um valor que a função retorna mas não vai ser usado, isso evita que o compilador reclame de variável não usada.

Pegadinhas

nil sozinho não tem tipo

var n = nil      // erro: cannot infer type
var n *int = nil // ok
var n []int      // ok, zero value de slice já é nil
Enter fullscreen mode Exit fullscreen mode

O compilador não consegue adivinhar o tipo a partir de nil, porque nil é válido para vários tipos diferentes (ponteiros, slices, maps, channels, interfaces, funções). Você precisa indicar o tipo.

:= em escopo aninhado faz shadow

err := outer()
if true {
    err := inner()   // nova variável no bloco interno
    _ = err
}
// err aqui ainda é o da função outer
Enter fullscreen mode Exit fullscreen mode

A variável de fora continua intocada. Shadowing costume causar efeitos indesejáveis na execução. Então é necessário muito cuidado, ou evitar.

Variável declarada e não usada = erro de compilação

func f() {
    x := 10   // declared and not used
}
Enter fullscreen mode Exit fullscreen mode

Sempre que você declara um variável, Go obriga o uso. Variáveis não usadas costumam ser sinal de bug ou código morto.

Próximo post irei falar sobre Zero Values, até lá.

Top comments (0)