2026-04-08

Comentário é aquela coisa que todo mundo aprende no primeiro dia de qualquer linguagem e já assume saber tudo sobre. Em Go, essa suposição custa caro. A linguagem fez do comentário uma ferramenta de primeira classe: um mesmo // pode ser só um lembrete pra você mesma, virar documentação pública, decidir o que o compilador vai incluir no binário, gerar código novo em tempo de build ou até virar código C de verdade.
Esse post é sobre os comentários em Go que fazem mais do que comentar.
Pra tirar isso do caminho: Go suporta dois tipos de comentário, exatamente como C.
// comentário de linha única
// que pode ocupar várias linhas coladas
/* comentário de bloco
que funciona bem pra blocos longos
ou quando você precisa de um comentário no meio de uma expressão */
x := /* inline no meio da linha */ 42
Na prática, // é o padrão em quase todo código Go idiomático, incluindo a biblioteca padrão. O /* */ fica reservado pra casos específicos, tipo documentação de pacote muito longa ou comentário inline pontual.
Até aqui nada mágico. A mágica começa quando você entende que o compilador e as ferramentas do Go leem alguns desses comentários.
godocA primeira regra não escrita do Go é: o comentário que precede uma declaração vira a documentação daquela declaração. Sem anotação, sem tag, sem nada. Basta colocar o comentário imediatamente acima, sem linha em branco no meio.
// Foo aplica a transformação foo na string recebida.
// Retorna erro se a string não puder ser foo'ada.
func Foo(s string) error {
// ...
}
Esse comentário não é só pra quem lê o código no editor. Ele alimenta o pkg.go.dev, o go doc no terminal e qualquer ferramenta que gere documentação de pacote Go. Exportou um símbolo (nome começando com maiúscula)? A convenção é que ele venha com um comentário descritivo, começando com o próprio nome do símbolo.
Funciona pra tudo que é exportável:
package objetos
// Objeto é um container genérico de alguma coisa.
type Objeto struct{}
// Processar aplica a lógica de processamento no Objeto.
// Retorna erro se o objeto estiver em estado inválido.
func (o Objeto) Processar() error {
return nil
}
// Lista contém todos os objetos registrados atualmente.
var Lista []Objeto
// MaxObjetos define o limite máximo de objetos permitidos.
const MaxObjetos = 50
Também existe a documentação de pacote: um comentário colocado imediatamente antes da declaração package, no início de qualquer arquivo do pacote. Convenção é colocar isso em um arquivo chamado doc.go quando a descrição for longa.
// Package objetos fornece um container genérico e seguro pra uso concorrente,
// com registro global e limites configuráveis.
package objetos
Uma sutileza importante: a primeira frase do comentário é o que aparece no índice do pkg.go.dev. Escreva a primeira frase pensando nela como um resumo de uma linha. O resto do parágrafo aparece só quando alguém abre a página completa.
Aqui a coisa começa a ficar divertida. Go tem um sistema de build constraints (também chamado de build tags) que permite dizer pro compilador: “esse arquivo só faz sentido em Linux”, ou “só compila quando a flag integration estiver ativa”. E tudo isso vive dentro de um comentário.
A sintaxe clássica (pré Go 1.17):
// +build linux
package main
// tudo aqui só compila em Linux
A regra é bem específica: o comentário // +build ... tem que estar nas primeiras linhas do arquivo, antes da declaração package, seguido por uma linha em branco antes do package. Se você esquecer a linha em branco, vira comentário normal e o compilador ignora.
A partir do Go 1.17 existe uma sintaxe nova, mais limpa:
//go:build linux
package main
As duas formas funcionam, e projetos modernos geralmente incluem ambas pra compatibilidade.
Os build constraints aceitam lógica booleana:
| Operador | Sintaxe clássica | Sintaxe nova | Significado |
|---|---|---|---|
| OR | // +build linux darwin | //go:build linux || darwin | compila em Linux ou macOS |
| AND | // +build windows,386 | //go:build windows && 386 | compila em Windows e 386 |
| NOT | // +build !windows | //go:build !windows | compila em qualquer coisa menos Windows |
Na sintaxe clássica, múltiplas linhas somam como AND:
// +build windows
// +build 386
package main
Isso é equivalente a windows AND 386.
Você não precisa se limitar a sistemas operacionais e arquiteturas. Qualquer palavra vira uma tag, e você ativa via go build -tags="minhatag".
//go:build integration
package repositorio
// testes que só rodam com: go test -tags=integration ./...
É assim que bibliotecas separam testes unitários de testes que precisam de banco de dados, por exemplo.
ignoreUma convenção útil: usar ignore como tag pra arquivos que você quer manter no repositório mas que o compilador não deve incluir em nenhuma build normal.
//go:build ignore
package main
// esse arquivo é um utilitário de geração manual
// rode com: go run arquivo.go
Bônus: se um arquivo se chama foo_linux.go, foo_windows.go ou foo_amd64.go, o Go aplica a constraint automaticamente, sem precisar do comentário. O sufixo é o próprio build tag. Combinações funcionam: foo_linux_amd64.go só compila em Linux amd64.
go generateEsse é o que mais surpreende gente que vem de outras linguagens. Go tem um subcomando chamado go generate que varre seu código procurando comentários especiais e roda os comandos que estão dentro deles. A sintaxe é:
//go:generate <comando> <argumentos>
Atenção ao detalhe: não pode ter espaço entre as duas barras e o go:generate. // go:generate não funciona. Tem que ser //go:generate, tudo colado.
O exemplo canônico é a ferramenta stringer, que gera automaticamente um método String() pra tipos enumerados. Partindo desse código:
package pedido
//go:generate stringer -type=Status
type Status int
const (
StatusPendente Status = iota
StatusPago
StatusCancelado
StatusEntregue
)
Quando você roda go generate ./..., o Go encontra aquele comentário, executa stringer -type=Status no diretório e gera um arquivo status_string.go com algo assim:
// Code generated by "stringer -type=Status"; DO NOT EDIT.
package pedido
func (i Status) String() string {
switch i {
case StatusPendente:
return "Pendente"
case StatusPago:
return "Pago"
case StatusCancelado:
return "Cancelado"
case StatusEntregue:
return "Entregue"
}
return "Status desconhecido"
}
A partir daí fmt.Println(StatusPago) imprime Pago, não o inteiro subjacente. Tudo sem você escrever uma linha do switch.
O go generate não roda automaticamente durante go build. Ele é um comando explícito que você deve rodar (idealmente automatizado via Makefile ou CI) antes de compilar. É intencional: o Go prefere que o código gerado seja commitado no repositório, não produzido em build time.
Você pode usar go:generate com qualquer comando, não só stringer. Ferramentas comuns incluem mockgen (geração de mocks), protoc-gen-go (Protobuf), sqlc (queries SQL tipadas) e afins.
Aqui a mágica sai de Go e entra em território estrangeiro. Cgo é o mecanismo oficial pra chamar código C a partir de Go, e a forma de escrever esse código C é… você adivinhou, dentro de comentários.
O padrão é colocar um bloco de comentário imediatamente antes do import "C" (sem linha em branco entre os dois). Tudo que estiver nesse comentário é passado literalmente pro compilador C.
package main
// #include <stdio.h>
// #include <errno.h>
import "C"
Você também pode escrever funções C inteiras ali dentro:
package main
// #include <stdio.h>
//
// static void imprimir(char *s) {
// printf("%s\n", s);
// }
import "C"
func main() {
C.imprimir(C.CString("oi, mundo C"))
}
Esse imprimir é uma função C real, compilada pelo compilador C do sistema, e invocada do Go como se fosse uma função Go qualquer.
#cgoAlém do código C puro, tem umas diretivas especiais começando com #cgo que configuram flags do compilador:
package imagens
// #cgo CFLAGS: -DPNG_DEBUG=1
// #cgo amd64 386 CFLAGS: -DX86=1
// #cgo LDFLAGS: -lpng
// #cgo pkg-config: png cairo
// #include <png.h>
import "C"
Traduzindo cada linha:
CFLAGS: -DPNG_DEBUG=1 passa essa flag pro compilador C em todas as arquiteturasamd64 386 CFLAGS: -DX86=1 passa essa flag só em amd64 e 386LDFLAGS: -lpng diz pro linker que precisa linkar com libpngpkg-config: png cairo usa o pkg-config pra descobrir automaticamente os flags corretos pra libpng e libcairoCgo é poderoso, mas tem um custo real de performance (cada chamada Go para C cruza uma fronteira) e complica bastante o cross-compile. Eu uso em último caso, quando não existe biblioteca Go nativa pro que eu preciso.
Dá pra escrever Go ignorando tudo isso. Você consegue viver anos usando // só como comentário normal e nunca esbarrar num //go:build. Mas entender esse mecanismo muda duas coisas na sua relação com a linguagem.
Primeiro, você passa a ler a biblioteca padrão de um jeito diferente. De repente aquele arquivo com //go:build darwin no topo faz sentido, e você entende por que o pacote net tem comportamentos sutilmente diferentes em cada sistema operacional. A biblioteca padrão é um curso prático de como usar build constraints bem.
Segundo, e mais importante: você percebe que Go tomou uma decisão de design deliberada e incomum. Enquanto outras linguagens inventam anotações (@override, @Component), atributos ([Serializable]), macros (#[derive]) ou decorators (@classmethod), Go reaproveitou uma coisa que toda linguagem já tem: o comentário. A sintaxe continua simples. Quem não conhece o mecanismo ignora sem perder nada. Quem conhece, ganha superpoderes sem poluir a gramática da linguagem.
É o tipo de decisão que parece simples demais pra funcionar, e depois de um tempo usando você entende por que funciona.
Um resumo pra deixar aberto enquanto você escreve:
| Comentário | Pra que serve | Quem processa |
|---|---|---|
// Foo faz tal coisa. acima de uma declaração | Vira documentação pública | go doc, pkg.go.dev |
// Package x faz Y. antes de package | Documentação do pacote inteiro | go doc, pkg.go.dev |
//go:build linux (ou // +build linux) | Restringe compilação a Linux | compilador |
//go:build integration | Compila só com -tags=integration | compilador |
//go:build ignore | Arquivo fica no repo mas não compila | compilador |
//go:generate cmd args | Roda cmd args no go generate | go generate |
Comentário antes de import "C" | Vira código C literal | compilador C via cgo |
// #cgo LDFLAGS: -lpng | Configura flags do cgo | cgo |
O próximo passo é olhar o código-fonte de um pacote grande da biblioteca padrão (recomendo o runtime ou o net) e procurar esses comentários. Você vai encontrar usos criativos que não cabem num post só.
E da próxima vez que alguém te disser que comentário não é código, você sorri e manda a pessoa abrir um arquivo .go.