A palavra-chave var declara uma variável. Dás-lhe um nome e um tipo, um valor inicial, ou ambos, como em var port int, var host = "localhost" ou var ratio float64 = 0.25. Uma variável declarada sem valor começa com o valor zero do seu tipo, que é 0 para números, "" para strings, false para booleanos e nil para ponteiros, slices, maps, channels, funções e interfaces. Dentro das funções, a maior parte do código Go usa antes a forma curta name := value, mas o var é a única maneira de declarar uma variável ao nível do pacote (especificação do Go).
Resumo rápido
var name Typedeclara uma variável com o seu valor zero.var name = valuededuz o tipo a partir do valor.var name Type = valuefaz as duas coisas. Um blocovar ( ... )agrupa várias declarações.- Todas as variáveis em Go são inicializadas. Não existe memória por inicializar, e tipos como
sync.Mutex,sync.WaitGroupebytes.Bufferestão prontos a usar com o seu valor zero. - O
:=declara e atribui num só passo, mas só dentro de funções. Ao nível do pacote precisas devar. - O
:=precisa de pelo menos um nome novo do lado esquerdo. Os nomes que já existem no mesmo âmbito são reutilizados, e os nomes de um âmbito exterior ficam tapados (shadowing). - Tapar uma variável exterior com
:=dentro de um blocoifé um bug comum. O compilador aceita-o, e o analisadorshadowdeteta-o. - Uma variável local não usada é um erro de compilação,
declared and not used. As variáveis não usadas ao nível do pacote compilam. - As variáveis ao nível do pacote são inicializadas por ordem de dependência antes de correr qualquer função
init(). Os erros sentinela e as expressões regulares compiladas são exemplos típicos de variáveis ao nível do pacote. var _ http.Handler = (*Server)(nil)obriga o compilador a verificar se um tipo implementa uma interface.
Como se declara uma variável em Go?
Há três formas, e um bloco agrupado que aceita qualquer combinação delas:
example.gogopackage main import ( "fmt" "time" ) var ( serviceName = "billing-api" maxRetries int64 = 3 requestCount int ) func main() { var port int // type only: starts at 0 var host = "localhost" // value only: the type is inferred as string var ratio float64 = 0.25 // both var readTimeout, writeTimeout time.Duration fmt.Println(serviceName, maxRetries, requestCount, port, host, ratio, readTimeout, writeTimeout) }
Isto imprime billing-api 3 0 0 localhost 0.25 0s 0s.
Com um tipo e sem valor, a variável recebe o valor zero desse tipo. Com um valor e sem tipo, a variável fica com o tipo do valor, e para uma constante sem tipo como "localhost" ou 0.25 esse é o tipo por omissão: string, int, float64, rune, bool ou complex128. Com ambos, o valor é convertido para o tipo declarado, e é assim que obténs um int64 ou um float32 a partir de um literal numérico simples. var maxRetries int64 = 3 é um int64. var maxRetries = 3 é um int.
Uma declaração pode listar vários nomes. var readTimeout, writeTimeout time.Duration declara duas variáveis do mesmo tipo, ambas a 0s. O bloco agrupado var ( ... ) é comum ao nível do pacote, onde mantém juntas as definições relacionadas. Ao contrário de um bloco const, não repete a expressão da linha anterior, por isso cada nome sem valor recebe o valor zero.
Como qualquer outro nome em Go, uma variável declarada ao nível do pacote é exportada quando o nome começa com maiúscula. http.DefaultClient e os.Args são variáveis exportadas ao nível do pacote. A palavra-chave package em Go explica como funcionam os nomes exportados entre pacotes.
O que é o valor zero em Go?
O valor zero é o valor que uma variável guarda quando a declaras sem a inicializar. O Go não tem memória por inicializar, por isso cada variável tem um valor definido desde o momento em que existe:
| Tipo | Valor zero |
|---|---|
bool | false |
int, float64 e os outros tipos numéricos | 0 |
string | "" (a string vazia) |
| ponteiros, slices, maps, channels, funções, interfaces | nil |
| arrays | cada elemento com o seu valor zero |
| structs | cada campo com o seu valor zero |
A mesma regra aplica-se à memória obtida com new, aos campos de uma struct que ficam de fora de um literal composto e aos elementos de um slice criado com make. Uma struct User descodificada de um JSON sem o campo email acaba com Email == "", e não com lixo.
Porque é que os valores zero são úteis em Go?
Os tipos bem desenhados funcionam com o seu valor zero, sem construtor, e a biblioteca padrão conta com isto. Um sync.Mutex com o valor zero é um mutex desbloqueado, um sync.WaitGroup é um grupo com contagem zero e um bytes.Buffer é um buffer vazio pronto para receber escritas. Uma struct construída com campos destes está pronta a usar assim que é declarada:
example.gogopackage main import ( "bytes" "fmt" "sync" ) type RequestCounter struct { mu sync.Mutex total int } func (c *RequestCounter) Inc() { c.mu.Lock() defer c.mu.Unlock() c.total++ } func main() { var counter RequestCounter // ready to use, no constructor needed var wg sync.WaitGroup for range 10 { wg.Go(counter.Inc) } wg.Wait() var buf bytes.Buffer fmt.Fprintf(&buf, "handled %d requests", counter.total) fmt.Println(buf.String()) }
Isto imprime handled 10 requests. Nenhuma das três variáveis precisou de uma função New. O Effective Go recomenda desenhar os teus próprios tipos da mesma forma, para que var x T chegue para começares a usar um T.
Que valores zero não podes usar logo?
Os maps e, em menor grau, os ponteiros e os channels. Um slice nil funciona com append, len e range, por isso var tags []string seguido de tags = append(tags, "go") não tem problema. Um map nil pode ser lido mas não escrito, e escrever num provoca um panic com assignment to entry in nil map. Desreferenciar um ponteiro nil provoca um panic, e enviar para um channel nil bloqueia para sempre. var vs make em Go percorre estes casos e explica quando deves recorrer ao make.
Qual é a diferença entre var e := em Go?
O := é a declaração curta de variáveis. retries := 3 significa o mesmo que var retries = 3: declara uma variável nova e deduz o tipo a partir do valor. Poupa escrever a palavra-chave e o tipo, e é a escolha habitual dentro das funções, sobretudo para valores devolvidos por uma chamada de função:
example.gogoresp, err := http.Get(healthURL)
A diferença está em onde cada uma é permitida. O := só funciona dentro do corpo de uma função. Ao nível do pacote, todas as declarações têm de começar com uma palavra-chave, por isso isto falha:
example.gogopackage main port := 8080 func main() {}
example.texttext./main.go:3:1: syntax error: non-declaration statement outside function body
var port = 8080 ao nível do pacote compila.
Dentro das funções as duas formas compilam, e a escolha resume-se a algumas convenções:
- Usa
:=quando tens um valor para atribuir, comocfg := loadConfig()oun, err := w.Write(body). - Usa
varquando queres o valor zero de propósito, comovar buf bytes.Buffer,var wg sync.WaitGroupouvar users []Userpara um slice que vais encher comappend. Ovardiz a quem lê que o ponto de partida é o valor zero. - Usa
varcom um tipo quando o tipo por omissão de um literal é o errado.var limit int64 = 1 << 40ouvar threshold float32 = 0.8lê-se melhor do quelimit := int64(1 << 40). - Usa
varquando a variável é atribuída mais tarde em ramos diferentes, como emvar status stringseguido de umif/elseque lhe dá o valor.
Podes redeclarar uma variável com := em Go?
Em parte. O := precisa de pelo menos uma variável nova do lado esquerdo, e qualquer nome já declarado no mesmo âmbito recebe uma atribuição em vez de ser declarado de novo. É isto que permite a uma função reutilizar um único err em várias chamadas:
example.gogopackage main import "os" func main() { f, err := os.Open("config.yaml") if err != nil { return } defer f.Close() info, err := f.Stat() // ok: info is new, err is reused _ = info f, err := os.Open("secrets.yaml") _ = f }
example.texttext./main.go:14:9: no new variables on left side of :=
info, err := f.Stat() compila porque info é nova. No último :=, tanto f como err já existem, por isso não há nada para declarar. Escreve f, err = os.Open("secrets.yaml") com um = simples para lhes atribuir valores. O var não tem esta exceção. var err error duas vezes no mesmo âmbito falha com err redeclared in this block.
O que é o shadowing de variáveis em Go?
O shadowing (sombreamento) acontece quando uma declaração num âmbito interior usa o mesmo nome que uma variável de um âmbito exterior. A variável interior esconde a exterior até o âmbito interior terminar. Cada bloco if, for, switch e { } abre um âmbito novo, e o := declara sempre no âmbito atual. Juntas, essas duas regras tornam este bug fácil de escrever:
example.gogopackage main import ( "fmt" "strconv" ) func listenAddr(rawPort string) (string, error) { port := 8080 if rawPort != "" { port, err := strconv.Atoi(rawPort) if err != nil { return "", fmt.Errorf("invalid PORT %q: %w", rawPort, err) } fmt.Println("using PORT", port) } return fmt.Sprintf(":%d", port), nil } func main() { addr, err := listenAddr("9090") fmt.Println(addr, err) }
Isto imprime using PORT 9090 e depois :8080 <nil>. Dentro do bloco if, err é nova, por isso o := é permitido, e declara também um port novo. O valor convertido vai para o port interior, que desaparece na chaveta de fecho. A função devolve o port exterior, que continua a valer 8080. A palavra-chave if em Go explica as regras de âmbito das instruções if que tornam isto possível.
A correção é atribuir à variável exterior em vez de declarar uma nova. Declara primeiro o err e usa =:
example.gogoif rawPort != "" { var err error port, err = strconv.Atoi(rawPort) if err != nil { return "", fmt.Errorf("invalid PORT %q: %w", rawPort, err) } }
As verificações por omissão do go vet não assinalam o shadowing, porque grande parte dele é intencional, como um err dentro de um if aninhado. O analisador shadow, da equipa do Go, assinala-o:
example.texttextgo install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow@latest go vet -vettool=$(which shadow) ./...
example.texttext./main.go:11:3: declaration of "port" shadows declaration at line 9
O golangci-lint pode fazer a mesma verificação através do linter govet com o shadow ativado.
Porque é que o Go diz "declared and not used"?
O compilador recusa qualquer variável local que nunca é lida. No exemplo acima, a linha do print é a única razão por que o port interior compila. Sem ela, o compilador reporta declared and not used: port e aponta para o bug. Atribuir um valor não conta como uso:
example.gogopackage main import "net/http" func main() { resp, err := http.Get("https://example.com") if err != nil { return } retries := 0 retries = 3 }
example.texttext./main.go:6:2: declared and not used: resp ./main.go:10:2: declared and not used: retries
A regra só abrange variáveis locais. Uma variável não usada ao nível do pacote, um parâmetro de função não usado e um resultado com nome não usado compilam todos. Quando precisas de guardar um valor que ainda não lês, atribui-o ao identificador em branco com _ = resp, ou deixa-o totalmente de fora com _, err := http.Get(url).
Como funcionam as variáveis ao nível do pacote em Go?
Uma variável declarada fora de qualquer função pertence ao pacote. É visível em todos os ficheiros do pacote, existe durante toda a vida do programa e é inicializada antes de o main começar. As variáveis ao nível do pacote são inicializadas por dependência, e não pela ordem em que aparecem no código:
example.gogopackage main import ( "errors" "fmt" ) var ( usersURL = apiBase + "/users" apiBase = apiHost + "/v2" apiHost = "https://api.internal" ) var ErrNotFound = errors.New("user not found") func init() { fmt.Println("init sees", usersURL) } func main() { err := fmt.Errorf("load user 42: %w", ErrNotFound) fmt.Println(usersURL, errors.Is(err, ErrNotFound)) }
Isto imprime init sees https://api.internal/v2/users e depois https://api.internal/v2/users true. O usersURL é declarado primeiro, mas depende do apiBase, que depende do apiHost, por isso o compilador inicializa-os pela ordem inversa. As variáveis que não dependem umas das outras são inicializadas pela ordem de declaração. Duas variáveis que dependem uma da outra são um erro, initialization cycle for a.
Depois de todas as variáveis ao nível do pacote terem o seu valor, correm as funções init(), pela ordem em que aparecem no código, ficheiro a ficheiro. Um pacote pode ter várias funções init(), até no mesmo ficheiro. Os pacotes importados são totalmente inicializados primeiro, por isso, quando o teu init() corre, todos os pacotes que importas já estão prontos. A palavra-chave import em Go explica como o init() interage com os imports em branco.
Porque é que os erros sentinela se declaram com var?
Porque o errors.New é uma chamada de função, e o seu resultado não pode ser uma constante. Um erro sentinela é uma variável ao nível do pacote com a qual quem chama compara através de errors.Is:
example.gogovar ( ErrNotFound = errors.New("user not found") ErrEmailTaken = errors.New("email already registered") )
O errors.Is compara valores de erro, e cada chamada a errors.New devolve um valor distinto, mesmo com o mesmo texto. É por isso que o erro tem de ser criado uma única vez, ao nível do pacote, e partilhado. Exemplos da biblioteca padrão incluem io.EOF, sql.ErrNoRows e http.ErrServerClosed.
O mesmo vale para outros valores que custam a construir e nunca mudam depois do arranque. var slugPattern = regexp.MustCompile("^[a-z0-9-]+$") compila a expressão regular uma vez, quando o programa arranca. Também provoca um panic no arranque se o padrão for inválido, em vez de falhar no primeiro pedido.
Deves usar variáveis globais em Go?
Para valores fixos depois do arranque, como erros sentinela, padrões compilados e tabelas de consulta, sim. Para estado mutável, normalmente não. Um var db *sql.DB ou var cfg Config ao nível do pacote é partilhado por todas as goroutines e por todos os testes do pacote. As escritas concorrentes precisam de um mutex, os testes não podem correr em paralelo com valores diferentes e nada na assinatura de uma função mostra que ela depende da variável global.
A alternativa habitual é pôr as dependências numa struct e passá-la de um lado para o outro:
example.gogotype Server struct { db *sql.DB logger *slog.Logger } func (s *Server) handleGetUser(w http.ResponseWriter, r *http.Request) { // uses s.db and s.logger instead of package-level variables }
Cada teste pode então construir o seu próprio Server, com a sua própria base de dados e o seu próprio logger.
O que significa var _ = em Go?
Declara uma variável que tem como nome o identificador em branco _. O valor é avaliado e depois deitado fora. O uso mais comum é uma verificação em tempo de compilação de que um tipo implementa uma interface:
example.gogotype Server struct{} func (s *Server) ServeHTTp(w http.ResponseWriter, r *http.Request) {} var _ http.Handler = (*Server)(nil)
O nome do método acima tem uma gralha, e a declaração transforma-a num erro de build:
example.texttext./main.go:9:22: cannot use (*Server)(nil) (value of type *Server) as http.Handler value in variable declaration: *Server does not implement http.Handler (missing method ServeHTTP) have ServeHTTp(http.ResponseWriter, *http.Request) want ServeHTTP(http.ResponseWriter, *http.Request)
Sem a verificação, a gralha só apareceria onde um *Server fosse passado como http.Handler, o que pode ser noutro pacote ou em lado nenhum. (*Server)(nil) é um ponteiro nil convertido para *Server, por isso a verificação não custa nenhuma alocação. Usa antes Server{} quando os métodos têm receivers por valor.
O identificador em branco também funciona em declarações com vários valores. var _, offset = time.Now().Zone() guarda só o segundo resultado.
Como se declaram várias variáveis de uma vez em Go?
Lista os nomes separados por vírgulas e dá-lhes uma lista de valores correspondente, ou uma chamada de função que devolva esse número de valores:
example.gogopackage main import ( "fmt" "strings" ) var host, port = "localhost", 8080 func main() { var user, domain, found = strings.Cut("[email protected]", "@") fmt.Println(host, port, user, domain, found) }
Isto imprime localhost 8080 ops levelupgo.dev true. Sem tipo, cada variável fica com o tipo do seu próprio valor, por isso host é uma string e port é um int. Com tipo, como em var minPort, maxPort int = 1024, 65535, todas partilham esse tipo.
A atribuição funciona da mesma maneira. a, b = b, a troca duas variáveis, porque o Go avalia todos os valores do lado direito antes de atribuir qualquer um deles.
Desde o Go 1.22, as variáveis de ciclo declaradas com := numa instrução for são variáveis novas em cada iteração. Uma closure ou goroutine que capture job em for _, job := range jobs vê o job dessa iteração, e não o último. A palavra-chave for em Go mostra o bug com goroutines que isto corrigiu e como verificar que comportamento um módulo tem.
Quando usar var ou const em Go?
Usa const para valores que se conhecem quando escreves o código e que nunca mudam, como timeouts, limites e nomes de cabeçalhos. Usa var para tudo o resto: valores calculados em tempo de execução, valores lidos da configuração e qualquer slice, map, struct ou ponteiro, já que estes não podem ser constantes. A palavra-chave const em Go explica as constantes sem tipo, o iota e os enums.
Onde entra o LevelUpGo
O LevelUpGo ensina Go através de exercícios que executam código Go real no navegador. O Go Basics apresenta o var, o := e os valores zero nas primeiras lições, a par das constantes e dos tipos. O Go Language Deep Dives tem uma lição sobre âmbito e shadowing onde encontras e corriges variáveis tapadas. O Training Ground tem exercícios curtos e independentes para praticares fora de um curso. Para as outras 24 palavras reservadas, vê Palavras-chave do Go: as 25 explicadas.
Perguntas frequentes
O var é uma palavra-chave em Go?
Sim. O var é uma das 25 palavras-chave reservadas do Go, por isso não o podes usar como nome de variável, de função ou de tipo. Inicia uma declaração de variável, seja uma única var name Type = value ou um bloco agrupado var ( ... ).
Qual é a diferença entre var e := em Go?
O := é uma forma curta de var que deduz sempre o tipo e só funciona dentro de funções. x := 5 e var x = 5 declaram ambos um int com o valor 5. O var é obrigatório ao nível do pacote e é a escolha mais clara quando queres o valor zero ou um tipo diferente do tipo por omissão do literal. O := também pode misturar nomes novos e existentes, desde que pelo menos um nome do lado esquerdo seja novo.
Qual é o valor zero de uma variável em Go?
É o valor que uma variável guarda quando é declarada sem inicializador: 0 para números, "" para strings, false para booleanos e nil para ponteiros, slices, maps, channels, funções e interfaces. Os arrays e as structs recebem o valor zero em cada elemento ou campo. O Go não tem variáveis por inicializar.
Podes usar := fora de uma função em Go?
Não. Ao nível do pacote, todas as declarações têm de começar com uma palavra-chave como var, const, type ou func, e port := 8080 falha com syntax error: non-declaration statement outside function body. Escreve antes var port = 8080.
Porque é que o Go dá o erro "declared and not used"?
Porque uma variável local não usada aponta muitas vezes para um bug, como uma variável tapada ou uma verificação de erro esquecida, e por isso o compilador do Go recusa-a. Atribuir um valor não conta como usar a variável. Lê a variável, remove-a ou atribui-a ao identificador em branco com _ = x. As variáveis não usadas ao nível do pacote e os parâmetros de função não usados são permitidos.
Fontes
- The Go Programming Language Specification, Variable declarations: https://go.dev/ref/spec#Variable_declarations
- The Go Programming Language Specification, Short variable declarations: https://go.dev/ref/spec#Short_variable_declarations
- The Go Programming Language Specification, The zero value: https://go.dev/ref/spec#The_zero_value
- The Go Programming Language Specification, Package initialization: https://go.dev/ref/spec#Package_initialization
- Effective Go, Variables and redeclaration: https://go.dev/doc/effective_go#redeclaration
- Effective Go, Allocation with new: https://go.dev/doc/effective_go#allocation_new
- Go FAQ, Can I stop these complaints about my unused variable/import?: https://go.dev/doc/faq#unused_variables_and_imports
- Fixing For Loops in Go 1.22: https://go.dev/blog/loopvar-preview
- Analisador shadow: https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/shadow
