Voltar ao blog

Palavras-chave do Go: as 25 palavras reservadas explicadas

O Go tem 25 palavras-chave, iguais desde o Go 1.0. Vê o que faz cada uma com um exemplo real e que nomes predeclarados, como any e len, não o são.

Palavras-chave do Go: as 25 palavras reservadas explicadas

O Go (Golang) tem 25 palavras-chave: break, case, chan, const, continue, default, defer, else, fallthrough, for, func, go, goto, if, import, interface, map, package, range, return, select, struct, switch, type e var. São reservadas, por isso não as podes usar como nomes de variáveis, funções ou tipos. A lista não mudou desde o Go 1.0, em 2012, e o Go 1.27 continua a ter as mesmas 25 (especificação do Go). O Python tem 35 palavras-chave, o Java tem 51 e o C++ tem mais de 80. A lista curta é intencional. O Go foi desenhado para ser pequeno o suficiente para caber inteiro na tua cabeça.

Resumo rápido

  • O Go tem exatamente 25 palavras-chave. A lista está na especificação do Go e é a mesma desde o Go 1.0.
  • Dividem-se em quatro grupos. Declarações (package, import, const, var, type, func), tipos compostos (struct, interface, map, chan), controlo de fluxo (12 palavras-chave, de if a return) e concorrência e limpeza (go, select, defer).
  • Há apenas uma palavra-chave para ciclos. O for cobre ciclos com contador, ciclos while, ciclos infinitos e, com range, a iteração sobre slices, maps, channels, inteiros e funções iteradoras.
  • any, int, string, error, nil, true, len e make não são palavras-chave. São identificadores predeclarados, por isso podes sombreá-los, embora normalmente não o devas fazer.
  • Os genéricos não acrescentaram palavras-chave. O Go 1.18 introduziu os parâmetros de tipo com nomes predeclarados (any, comparable) em vez de novas palavras reservadas.

A lista completa das palavras-chave do Go

Palavra-chaveCategoriaO que faz
packageDeclaraçãoDá nome ao pacote a que o ficheiro pertence
importDeclaraçãoImporta outros pacotes
constDeclaraçãoDeclara uma constante de tempo de compilação
varDeclaraçãoDeclara uma variável
typeDeclaraçãoDeclara um tipo com nome ou um alias
funcDeclaraçãoDeclara uma função, um método ou uma função literal
structTipo compostoAgrupa campos com nome num só tipo
interfaceTipo compostoDeclara um conjunto de métodos (ou uma restrição de tipo)
mapTipo compostoDeclara um tipo de hash map
chanTipo compostoDeclara um tipo de channel
ifControlo de fluxoExecuta um bloco quando uma condição é verdadeira
elseControlo de fluxoExecuta um bloco quando a condição do if é falsa
forControlo de fluxoO único ciclo do Go
rangeControlo de fluxoItera sobre slices, maps, strings, channels, inteiros e iteradores
switchControlo de fluxoEscolhe um ramo entre vários
caseControlo de fluxoUm ramo de um switch ou de um select
defaultControlo de fluxoO ramo que corre quando nenhum case corresponde
fallthroughControlo de fluxoContinua para o case seguinte de um switch
breakControlo de fluxoSai de um for, switch ou select
continueControlo de fluxoSalta para a iteração seguinte do ciclo
gotoControlo de fluxoSalta para uma etiqueta na mesma função
returnControlo de fluxoSai de uma função, opcionalmente com valores
goConcorrênciaInicia uma função numa nova goroutine
selectConcorrênciaEspera por várias operações em channels
deferLimpezaExecuta uma chamada quando a função retorna

Quais são as palavras-chave de declaração do Go?

O Go tem seis palavras-chave de declaração, e este pequeno carregador de configuração usa todas:

example.gogo
package main

import (
	"fmt"
	"time"
)

const defaultTimeout = 5 * time.Second

var maxRetries = 3

type Config struct {
	Addr    string
	Timeout time.Duration
}

func newConfig(addr string) Config {
	return Config{Addr: addr, Timeout: defaultTimeout}
}

func main() {
	cfg := newConfig(":8080")
	fmt.Println(cfg.Addr, cfg.Timeout, maxRetries)
}

package

Todos os ficheiros Go começam com uma cláusula package. Todos os ficheiros de um diretório partilham o mesmo nome de pacote. O pacote chamado main é especial: compila para um executável, e a sua função main é o ponto onde o programa arranca. Qualquer outro nome compila para uma biblioteca que outros pacotes importam. A palavra-chave package em Go aprofunda as regras de nomes, a visibilidade e os pacotes _test.

import

O import torna disponíveis no ficheiro os nomes exportados de outro pacote, como fmt.Println ou http.ListenAndServe. Podes agrupar os imports entre parênteses, dar outro nome a um (yaml "gopkg.in/yaml.v3") ou importar um pacote só pelos seus efeitos secundários com _. O Go recusa-se a compilar um ficheiro com um import não usado, por isso um ficheiro nunca lista um pacote que não usa. A palavra-chave import em Go aprofunda os aliases, os imports em branco e com ponto, e os ciclos de importação.

const

O const declara um valor fixo em tempo de compilação, como um timeout ou o nome de um header. As constantes podem não ter tipo. const maxBodyBytes = 1 << 20 não tem tipo até ser usada, por isso funciona como int ou como int64 sem conversão. Dentro de um bloco const, o identificador predeclarado iota conta a partir de 0, e é assim que o Go escreve enums. A palavra-chave const em Go aprofunda as constantes com e sem tipo, o iota e os enums.

var

O var declara uma variável com um tipo explícito, um valor inicial ou ambos. Todas as variáveis começam com o valor zero do seu tipo: 0, "", false ou nil. Dentro das funções, a maior parte do código Go usa antes a forma curta name := value. O var continua a ser a escolha certa ao nível do pacote e quando queres o valor zero de propósito, como em var buf bytes.Buffer. A palavra-chave var em Go aprofunda os valores zero, o :=, o shadowing e as variáveis ao nível do pacote.

type

O type declara um novo tipo com nome, como uma struct, uma interface ou uma versão com nome de um tipo existente, por exemplo type UserID int64. O novo tipo pode ter os seus próprios métodos. Com =, declara antes um alias (type Handler = http.Handler), que é apenas um segundo nome para o mesmo tipo. A palavra-chave type em Go aprofunda os tipos definidos, os aliases, os métodos e os tipos genéricos.

func

O func declara funções e métodos, e também cria funções literais (closures). Um método tem um recetor antes do nome: func (s *Server) Start() error. As funções em Go podem devolver vários valores, e é assim que os erros são reportados: func parse(body []byte) (Request, error). A palavra-chave func em Go aprofunda os métodos, as closures, as funções variádicas e as functional options.

Que palavras-chave do Go constroem tipos compostos?

Quatro palavras-chave constroem tipos a partir de outros tipos. Os arrays, slices e ponteiros também são tipos compostos, mas usam símbolos ([N]T, []T, *T) em vez de palavras-chave.

example.gogo
type Notifier interface {
	Notify(ctx context.Context, msg string) error
}

type SlackNotifier struct {
	Channel string
}

func (s SlackNotifier) Notify(ctx context.Context, msg string) error {
	fmt.Printf("#%s: %s\n", s.Channel, msg)
	return nil
}

func main() {
	var n Notifier = SlackNotifier{Channel: "alerts"}

	statusCounts := map[int]int{200: 41, 500: 2}
	events := make(chan string, 10)

	events <- fmt.Sprintf("%d server errors", statusCounts[500])
	n.Notify(context.Background(), <-events)
}

struct

O struct agrupa campos com nome num só tipo. É o substituto das classes no Go: associas métodos a um tipo struct e reutilizas comportamento incorporando uma struct noutra em vez de herdar dela. As struct tags como json:"email" dizem a pacotes como o encoding/json como codificar cada campo. A palavra-chave struct em Go aprofunda as struct tags, os receivers por valor e por ponteiro, a incorporação e como comparar e copiar structs.

interface

O interface declara um conjunto de métodos. Um tipo satisfaz uma interface só por ter esses métodos, sem nenhuma declaração implements, por isso o SlackNotifier acima é um Notifier sem o dizer. Desde o Go 1.18, as interfaces também descrevem restrições de tipo para genéricos, como interface{ ~int | ~float64 }. A palavra-chave interface em Go aprofunda a satisfação implícita, as interfaces nil, as asserções de tipo e as restrições.

map

O map declara um tipo de hash map, escrito map[KeyType]ValueType. Crias um com um literal ou com make, e um map nil entra em panic quando escreves nele. Ler uma chave inexistente devolve o valor zero, e a forma com dois valores count, ok := statusCounts[404] diz-te se a chave existia. A especificação deixa a ordem de iteração por definir, e o runtime torna-a aleatória de propósito. var vs make em Go explica porque é que um map nil entra em panic ao ser escrito e um slice nil não. A palavra-chave map em Go aprofunda o idioma comma-ok, a ordem de iteração, os conjuntos, o acesso concorrente e o pacote maps.

chan

O chan declara um tipo de channel, o canal que as goroutines usam para passar valores umas às outras. chan<- Job só envia e <-chan Job só recebe, o que permite à assinatura de uma função dizer que direção usa. Os channels são criados com make, e um channel com buffer (make(chan string, 10)) guarda valores até alguém os receber. A palavra-chave chan em Go aprofunda os channels com e sem buffer, o fecho, os deadlocks e os padrões com select.

Que palavras-chave do Go controlam o fluxo de um programa?

Doze palavras-chave tratam das ramificações e dos ciclos. O Go deixa de fora while, do, try, catch e o operador ternário. Os erros são valores de retorno comuns que verificas com if.

example.gogo
func parse(body []byte) (Request, error) {
	var req Request
	if err := json.Unmarshal(body, &req); err != nil {
		return Request{}, fmt.Errorf("decode request: %w", err)
	}
	if req.Email == "" {
		return Request{}, errors.New("email is required")
	}
	return req, nil
}

func countErrors(lines []string) int {
	errs := 0
	for _, line := range lines {
		if strings.HasPrefix(line, "#") {
			continue
		}
		if line == "EOF" {
			break
		}
		if strings.Contains(line, "level=error") {
			errs++
		}
	}
	return errs
}

if

O if executa um bloco quando uma condição é verdadeira. A condição não precisa de parênteses, mas as chavetas são obrigatórias. Um if pode começar com uma instrução curta, e a variável que ela declara só existe dentro do if e dos seus ramos else. O código Go usa esta forma constantemente, como em if err := json.Unmarshal(body, &req); err != nil. A palavra-chave if em Go aprofunda as condições só com bool, as instruções de inicialização, o shadowing, os retornos antecipados e a ausência do operador ternário.

else

O else corre quando a condição do if é falsa, e o else if encadeia mais condições. O Go idiomático usa o else menos do que a maioria das linguagens. Quando o ramo do if termina em return, o resto da função já é o caso else, por isso o código Go mantém o caminho feliz sem indentação e retorna cedo em caso de erro. A palavra-chave else em Go aprofunda as cadeias else if, o âmbito das variáveis do if, os retornos antecipados e a ausência do operador ternário.

for

O for é a única palavra-chave de ciclo do Go. for i := 0; i < n; i++ é o clássico ciclo com contador, for queue.Len() > 0 é um ciclo while e for { ... } repete para sempre até alguma coisa sair dele. Desde o Go 1.22, em módulos cujo go.mod declara go 1.22 ou posterior, cada iteração recebe a sua própria cópia da variável do ciclo, por isso as closures e as goroutines iniciadas num ciclo deixaram de partilhar uma única variável. A palavra-chave for em Go aprofunda as três formas do ciclo, o range, as variáveis de ciclo do Go 1.22, as etiquetas e os erros comuns com ciclos.

range

O range faz um ciclo for iterar sobre uma coleção. Dá-te o índice e o valor em slices e arrays, a chave e o valor em maps, o offset em bytes e a rune em strings, e os valores recebidos em channels. O Go 1.22 acrescentou a iteração sobre um inteiro (for attempt := range 3 corre com 0, 1 e 2), e o Go 1.23 acrescentou a iteração sobre funções iteradoras, como a que maps.Keys(m) devolve.

switch

O switch escolhe um ramo entre vários. Ao contrário do C e do Java, um switch em Go para depois do case que corresponde, por isso nunca precisas de um break no fim de cada case. Um switch sem expressão funciona como uma cadeia if/else if mais limpa, e um type switch (switch v := x.(type)) ramifica conforme o tipo dinâmico de um valor de interface.

case

O case marca um ramo de um switch ou de um select. Um único case pode listar vários valores: case "GET", "HEAD":. Num switch, os cases são verificados de cima para baixo, e ganha a primeira correspondência.

default

O default é o ramo que corre quando nenhum case corresponde. Pode aparecer em qualquer ponto do switch, embora a convenção o ponha em último. Num select, um case default torna toda a instrução não bloqueante: se nenhum channel estiver pronto, o default corre de imediato.

fallthrough

O fallthrough faz um switch continuar para o corpo do case seguinte sem verificar a condição desse case. É raro em código real, e é o único sítio onde um switch em Go se comporta como em C. As permissões por níveis são um caso em que se lê bem:

example.gogo
func permissions(role string) []string {
	var perms []string
	switch role {
	case "admin":
		perms = append(perms, "delete")
		fallthrough
	case "editor":
		perms = append(perms, "write")
		fallthrough
	case "viewer":
		perms = append(perms, "read")
	default:
		return nil
	}
	return perms
}

permissions("editor") devolve [write read] e permissions("admin") devolve [delete write read]. O fallthrough tem de ser a última instrução de um case, e não é permitido num type switch.

break

O break sai do for, switch ou select mais interior. Esta última parte é uma armadilha comum: um break dentro de um select só sai do select, não do ciclo à volta dele. Para sair do ciclo, põe-lhe uma etiqueta e escreve break loop:

example.gogo
func drain(ctx context.Context, events <-chan string) {
loop:
	for {
		select {
		case ev, ok := <-events:
			if !ok {
				break loop
			}
			fmt.Println("event:", ev)
		case <-ctx.Done():
			break loop
		}
	}
	fmt.Println("drained")
}

continue

O continue salta o resto da iteração atual e começa a seguinte. No countErrors acima, salta as linhas de comentário. Tal como o break, pode receber uma etiqueta para continuar um ciclo exterior a partir de um ciclo aninhado.

goto

O goto salta para uma etiqueta na mesma função. Não pode saltar para dentro de um bloco nem por cima de uma declaração de variável, o que elimina a maior parte dos problemas que o goto tem em C. Raramente o vais escrever. Aparece sobretudo em código gerado e em algumas partes de baixo nível da biblioteca padrão. O próprio backend do LevelUpGo tem cerca de 30.000 linhas de Go, sem contar testes e dados de seed. Usa goto e fallthrough zero vezes, contra mais de 250 ciclos range e mais de 40 chamadas defer.

return

O return termina uma função e devolve os seus resultados. As funções em Go devolvem muitas vezes dois valores, um resultado e um error, e quem chama verifica o erro de imediato. Com parâmetros de resultado com nome, um return sem argumentos devolve os valores atuais desses parâmetros, mas a maioria dos guias de estilo reserva isso para funções curtas.

Que palavras-chave do Go tratam da concorrência e da limpeza?

As três últimas palavras-chave são go, select e defer. Juntamente com chan, são o que um servidor em Go usa para executar trabalho em paralelo e limpar a seguir.

example.gogo
func startWorkers(ctx context.Context, n int, jobs <-chan string, results chan<- []byte) {
	for range n {
		go worker(ctx, jobs, results)
	}
}

func worker(ctx context.Context, jobs <-chan string, results chan<- []byte) {
	for {
		select {
		case url, ok := <-jobs:
			if !ok {
				return
			}
			body, err := fetch(ctx, url)
			if err != nil {
				log.Printf("fetch %s: %v", url, err)
				continue
			}
			results <- body
		case <-ctx.Done():
			return
		}
	}
}

func fetch(ctx context.Context, url string) ([]byte, error) {
	ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
	defer cancel()

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
	if err != nil {
		return nil, err
	}
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	return io.ReadAll(resp.Body)
}

go

O go inicia uma chamada de função numa nova goroutine e continua sem esperar por ela. O startWorkers acima executa go worker(ctx, jobs, results) num ciclo, o que te dá uma worker pool. As goroutines começam com poucos kilobytes de stack, por isso um servidor consegue executar centenas de milhares delas. A instrução go descarta os valores de retorno da função, por isso os resultados voltam através de channels, e um sync.WaitGroup ou um errgroup espera que o trabalho termine.

select

O select espera por várias operações em channels e executa a primeira que estiver pronta. Se várias estiverem prontas ao mesmo tempo, escolhe uma ao acaso. O worker acima usa-o para pegar no próximo job ou parar quando o context é cancelado. Combinar um channel com ctx.Done() ou time.After é a forma como o código Go acrescenta timeouts e cancelamento a qualquer coisa que bloqueie.

defer

O defer agenda uma chamada para correr quando a função envolvente retorna, quer retorne normalmente, quer entre em panic. Mantém a limpeza junto do código que precisa dela: defer resp.Body.Close(), defer mu.Unlock(), defer cancel(). As chamadas diferidas correm por ordem inversa (a última a entrar é a primeira a sair), e os seus argumentos são avaliados quando a linha do defer corre, não no fim. Um defer dentro de um ciclo só corre quando a função inteira retorna, e esse é um dos erros comuns em Go que vale a pena conhecer cedo.

O que não são palavras-chave no Go?

Muitos nomes que parecem embutidos na linguagem são identificadores predeclarados, não palavras-chave. A especificação do Go lista-os no universe block, o bloco que envolve todos os pacotes (especificação do Go):

CategoriaIdentificadores predeclarados
Tiposany, bool, byte, comparable, complex64, complex128, error, float32, float64, int, int8, int16, int32, int64, rune, string, uint, uint8, uint16, uint32, uint64, uintptr
Constantestrue, false, iota
Valor zeronil
Funçõesappend, cap, clear, close, complex, copy, delete, imag, len, make, max, min, new, panic, print, println, real, recover

A diferença importa porque os identificadores predeclarados podem ser redeclarados num âmbito interior. Isto compila:

example.gogo
func logAddrChange(cur, next Config) {
	old, new := cur.Addr, next.Addr
	if old != new {
		log.Printf("addr changed from %s to %s", old, new)
	}
}

func countLines(lines []string) int {
	len := len(lines)
	return len
}

Chamar new a uma variável é comum em código que compara valores antigos e novos, e funciona. Mas depois de len := len(lines), qualquer chamada len(...) posterior nessa função falha com invalid operation: cannot call len (variable of type int): int is not a function. Uma palavra-chave não pode ser usada assim de todo. var := 3 falha com syntax error: unexpected :=, expected name. Linters como o predeclared do golangci-lint assinalam built-ins sombreados, porque código como o countLines compila mas confunde quem o lê a seguir.

Manter estes nomes fora da lista de palavras-chave permite ao Go crescer sem partir programas existentes. O any (Go 1.18) e o min, o max e o clear (Go 1.21) foram acrescentados como identificadores predeclarados, por isso qualquer código que já usava esses nomes como variáveis continuou a compilar.

Onde entra o LevelUpGo

O LevelUpGo ensina Go através de exercícios que executam código Go real no navegador. O curso Go Basics cobre as palavras-chave de declaração e de controlo de fluxo, de var e const a switch e for. O Concurrency Fundamentals cobre go, chan, select e defer, e o Simplification mostra quando um switch se lê melhor do que uma cadeia de if. O Training Ground tem exercícios curtos e avulsos para as praticares fora de um curso.

Perguntas frequentes

Quantas palavras-chave tem o Go?

O Go tem 25 palavras-chave: break, case, chan, const, continue, default, defer, else, fallthrough, for, func, go, goto, if, import, interface, map, package, range, return, select, struct, switch, type e var. A lista está definida na especificação do Go e não mudou desde o Go 1.0.

O any é uma palavra-chave no Go?

Não. O any é um identificador predeclarado, um alias de interface{} acrescentado no Go 1.18. O mesmo vale para int, string, error, nil, true, false, iota e funções built-in como len, make e append. Podes redeclará-los num âmbito interior, embora os linters avisem quando o fazes.

Posso usar uma palavra-chave do Go como nome de variável?

Não. As palavras-chave são reservadas, por isso var := 3 ou func type() é um erro de sintaxe. A solução habitual é uma pequena variação: o código Go usa muitas vezes typ ou kind em vez de type, pkg em vez de package e fn em vez de func.

O Go acrescentou palavras-chave desde o Go 1.0?

Não. As 25 palavras-chave do Go 1.27 são as mesmas 25 que vieram com o Go 1.0, em março de 2012. Os genéricos, o range sobre inteiros, as funções iteradoras e os métodos genéricos no Go 1.27 chegaram todos sem novas palavras-chave. A promessa de compatibilidade do Go 1 torna improváveis novas palavras-chave, porque uma nova palavra reservada partiria todos os programas que já a usam como nome.

Porque é que o Go tem tão poucas palavras-chave?

Os criadores quiseram assim. A FAQ do Go diz que «tentaram reduzir a desordem e a complexidade» e que a «sintaxe é limpa e leve em palavras-chave» do Go. É por isso que o Go tem uma só palavra-chave de ciclo em vez de três, e que os erros são valores de retorno em vez de try/catch. O resultado é uma linguagem que se aprende depressa, e o código Go de bases de código diferentes lê-se de forma muito parecida.

Fontes

Escreve Go como um engenheiro sénior

Lições interativas no navegador. As primeiras são grátis.

Experimenta uma lição grátisOu cria uma conta gratuita