Um primeiro programa em Go precisa de uma ferramenta de linha de comando, um arquivo go.mod e um pacote chamado main. Veja o que go run, go build, gofmt e go vet fazem, e por que Go não compila código com um import sem uso.
Go vem como uma única ferramenta chamada go. Ela compila seu código, roda, formata, procura erros comuns e gerencia as dependências. Você não escolhe um sistema de build nem um formatador, porque eles já vêm na caixa.
Este post configura essa ferramenta, escreve um primeiro programa e passa pelos comandos que você vai digitar todo dia. Todo programa abaixo rodou no Go 1.26, e a saída foi colada direto da execução.
Instalando o Go e conferindo se funciona
O Go se instala pela página de downloads em go.dev/dl, ou pelo gerenciador de pacotes do seu sistema. De um jeito ou de outro, você termina com um comando go no seu path. Confira:
$ go version
go version go1.26.2 linux/amd64
A última parte diz o seu sistema operacional e processador, então o seu pode mostrar darwin/arm64 ou windows/amd64. O que importa é aparecer go1.26 ou mais novo.
Essa é a configuração inteira. Tutoriais antigos mandam criar uma pasta GOPATH e guardar todo o seu código dentro dela. Você não precisa mais fazer isso. Desde que os módulos chegaram, um projeto Go pode ficar em qualquer pasta do seu disco.
O GOPATH ainda existe, mas é um lugar que o Go usa para si mesmo. Ele guarda um cache das dependências baixadas, e é onde o go install coloca os programas que compila. Dá para passar muito tempo sem olhar lá dentro.
Seu primeiro programa
Um projeto Go começa com uma pasta e um comando go mod init, que transforma essa pasta em um módulo:
$ mkdir hello
$ cd hello
$ go mod init example.com/hello
go: creating new go.mod: module example.com/hello
Agora crie um arquivo chamado main.go nessa pasta:
package main
import "fmt"
func main() {
fmt.Println("Hello, Go")
}
Ele imprime:
Hello, Go
Rode com go run ., onde o ponto quer dizer “o pacote desta pasta”. Três linhas fazem todo o trabalho.
package main diz a qual pacote este arquivo pertence. Todo arquivo Go começa com uma linha de pacote. Um pacote chamado main é especial: é ele que vira um programa que você pode rodar. Qualquer outro nome cria uma biblioteca que outro código importa.
import "fmt" traz o pacote de formatação da biblioteca padrão. fmt.Println imprime seus argumentos com espaços entre eles e uma quebra de linha no fim.
func main() é onde o programa começa. Ela não recebe argumentos e não devolve nada. Quando main retorna, o programa termina.
As duas metades são obrigatórias. Um pacote main sem uma função main não vira programa, e o Go avisa:
package main
import "fmt"
func Main() {
fmt.Println("Hello, Go")
}
O build falha com:
function main is undeclared in the main package
O M maiúsculo fez dela outra função. Nomes em Go diferenciam maiúsculas de minúsculas e, como você vai ver daqui a pouco, a caixa da primeira letra tem significado.
go run e go build
O Go dá duas formas de rodar um programa: go run para testar agora, e go build para gerar algo que você pode entregar a outra pessoa.
$ go run .
Hello, Go
$ go build
$ ls
go.mod hello main.go
$ ./hello
Hello, Go
O go run compila o programa para um arquivo temporário, roda e joga o arquivo fora. O go build compila e guarda o resultado, um arquivo com o nome do último elemento do caminho do módulo, hello.
Esse arquivo é o programa inteiro. Copie para outra máquina Linux do mesmo tipo e ele roda lá, mesmo que o Go nunca tenha sido instalado nela. Não tem runtime para instalar antes nem pasta de bibliotecas para mandar junto.
Você também pode compilar para um sistema operacional diferente do seu, definindo duas variáveis de ambiente:
$ GOOS=windows GOARCH=amd64 go build
$ ls
go.mod hello hello.exe main.go
hello.exe é um programa Windows, compilado no Linux, sem nada extra instalado.
Explicado como se você tivesse dez anos
Pense numa receita e num bolo.
Um script, como um programa Python, é uma receita. Para ter bolo, a pessoa que recebe precisa de uma cozinha, um forno e alguém que saiba ler receitas. Se o forno dela for de outro modelo, o bolo pode sair errado.
Um programa Go compilado é o bolo pronto numa caixa. O cozimento já aconteceu no seu computador. A pessoa que recebe só abre a caixa. Ela não precisa de cozinha.
A versão precisa
O compilador Go transforma seu código-fonte, e todo pacote que ele importa, em código de máquina para um sistema operacional e um processador. Ele junta tudo isso em um único arquivo executável. O runtime do Go, que roda o garbage collector e agenda as goroutines, é compilado dentro desse mesmo arquivo.
Um programa como este, que só usa pacotes em Go puro, é linkado estaticamente: ele não carrega bibliotecas compartilhadas ao iniciar. É por isso que o arquivo tem alguns megabytes para um programa que imprime uma linha, e também é por isso que você pode copiá-lo para qualquer lugar que bata com o seu GOOS e GOARCH.
Um programa Python ou JavaScript funciona do jeito oposto. Você entrega o código-fonte, e a máquina precisa ter o interpretador certo instalado para rodar.
Onde a analogia falha: um bolo na caixa pode ser comido em qualquer lugar, mas um binário Go só roda no sistema para o qual foi compilado. Um binário Linux não inicia no Windows. Você assa um bolo separado para cada tipo de cozinha, e o Go deixa isso barato com GOOS e GOARCH.
O que o go.mod registra
O arquivo go.mod que o go mod init criou é curto. Aqui está ele inteiro:
$ cat go.mod
module example.com/hello
go 1.26.2
A linha module é o caminho do módulo, e é o prefixo de todo caminho de import dentro dele. Parece um endereço web porque módulos publicados costumam ser encontrados em um. Para um projeto que nunca sai da sua máquina, qualquer nome serve, mas o estilo de domínio evita problemas se você publicar depois.
A linha go diz para qual versão da linguagem Go este módulo foi escrito. O go mod init preencheu a versão exata que rodou, com o número de patch, então a sua vai bater com o seu toolchain.
Essa linha faz mais do que documentar. Ela liga e desliga recursos da linguagem. Mude o main.go para um laço que usa range sobre um inteiro, for i := range 3, um recurso adicionado no Go 1.22. Depois ajuste a linha go para uma versão mais antiga e rode:
$ go mod edit -go=1.21
$ go run .
# example.com/hello
./main.go:6:17: cannot range over 3 (untyped int constant): requires go1.22 or later (-lang was set to go1.21; check go.mod)
O compilador era o Go 1.26 o tempo todo. Ele recusou o laço porque o go.mod dizia que o código mira a 1.21. É assim que módulos antigos continuam compilando do mesmo jeito enquanto o Go ganha recursos, e o erro ainda diz qual arquivo conferir.
Quando seu módulo começar a usar código de outras pessoas, o go.mod também lista essas dependências e suas versões. Isso volta na parte sobre pacotes e módulos.
Imports e nomes exportados
A biblioteca padrão é um conjunto de pacotes que você importa pelo caminho, como fmt e strings. Depois de importar, você chama o que tem dentro com o nome do pacote, um ponto e o nome:
package main
import (
"fmt"
"strings"
)
func main() {
title := "hello, go"
fmt.Println(strings.ToUpper(title))
fmt.Println(strings.Fields(" one two three "))
fmt.Println(strings.Contains(title, "go"))
}
Ele imprime:
HELLO, GO
[one two three]
true
Dois ou mais imports vão num bloco entre parênteses, um por linha. strings.ToUpper devolve uma cópia em maiúsculas, strings.Fields separa nas sequências de espaços, e strings.Contains diz se uma string aparece dentro de outra.
Todo nome que você chamou ali começa com letra maiúscula: Println, ToUpper, Fields, Contains. Não é questão de estilo. Em Go, um nome que começa com letra maiúscula é exportado, ou seja, código fora do pacote pode usá-lo. Um nome que começa com letra minúscula é privado ao seu pacote.
Não existe palavra-chave public ou private. A primeira letra é a regra inteira. Erre e o programa não compila:
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Println(strings.toUpper("quiet"))
}
O build falha com:
./main.go:9:22: undefined: strings.toUpper (but have ToUpper)
O compilador não disse só que o nome não existe. Ele achou o nome exportado que difere apenas na caixa e sugeriu esse nome. Vale ler mensagens de erro assim com calma, porque muitas vezes elas trazem a correção.
A regra vale para o seu código também. Dentro do pacote main, uma função chamada greeting e outra chamada Greeting funcionam as duas, porque nada fora de main importa esse pacote. Começa a importar quando você divide o código em pacotes seus.
gofmt: um formato para todo mundo
O Go tem um layout oficial para código-fonte, e uma ferramenta chamada gofmt aplica esse layout. Aqui está um programa escrito sem nenhum cuidado com o layout:
package main
import "fmt"
func main() {
x:=[]int{1,2,3}
for _,v:=range x {fmt.Println( v )}
}
Ele compila e roda. Agora deixe o gofmt arrumar:
$ gofmt -l .
main.go
$ gofmt -w main.go
O gofmt -l lista os arquivos com layout fora do padrão, e o gofmt -w reescreve esses arquivos no lugar. Depois da reescrita, o arquivo fica assim:
package main
import "fmt"
func main() {
x := []int{1, 2, 3}
for _, v := range x {
fmt.Println(v)
}
}
Ele imprime:
1
2
3
Tabs na indentação, espaços em volta de := e depois das vírgulas, uma linha em branco entre as seções, e o corpo do laço em linhas próprias. Não há opções para mudar nada disso.
Essa é a ideia. Com um formato só, ninguém num time discute onde vai a chave, e os diffs de code review mostram só mudanças reais. Todo projeto Go que você abre parece familiar. A maioria dos editores roda o gofmt quando você salva, então você raramente chama na mão. O comando go fmt ./... faz o mesmo para todo pacote de um módulo.
go vet: um bug que compila
Alguns erros são Go válido, mas quase certamente estão errados, e o go vet procura por eles. Aqui está um comum, com os argumentos do Printf na ordem errada:
package main
import "fmt"
func main() {
name := "Ada"
age := 36
fmt.Printf("%s is %d years old\n", age, name)
}
%s espera uma string e %d espera um número inteiro. Este programa passa os dois invertidos, e mesmo assim compila e roda:
$ go run .
%!s(int=36) is %!d(string=Ada) years old
$ go vet
main.go:8:14: fmt.Printf format %s has arg age of wrong type int
O compilador não consegue pegar isso, porque o Printf aceita quaisquer valores depois da string de formato. Em tempo de execução, o fmt também não quebra. Ele imprime %!s(int=36), o jeito dele de dizer “você pediu uma string e me deu um int”. Uma linha assim pode ficar meses num arquivo de log antes de alguém notar.
O go vet lê a string de formato, casa cada verbo com seu argumento, e aponta a linha e o problema. Troque os argumentos e o programa fica certo:
package main
import "fmt"
func main() {
name := "Ada"
age := 36
fmt.Printf("%s is %d years old\n", name, age)
}
Ele imprime:
Ada is 36 years old
O go test roda um conjunto dessas verificações do vet automaticamente, mas vale rodar go vet você mesmo antes de fazer commit. Todo programa desta série passa nele.
Variáveis e imports sem uso não compilam
O Go trata uma variável local sem uso ou um import sem uso como erro de compilação, não como aviso. Este programa declara uma variável que nunca lê e importa um pacote que nunca chama:
package main
import (
"fmt"
"os"
)
func main() {
count := 3
fmt.Println("hello")
}
O build falha com:
./main.go:5:2: "os" imported and not used
./main.go:9:2: declared and not used: count
As duas mensagens apontam a linha e a coluna exatas. O programa não compila até você remover count e o import de os, ou usar os dois.
Isso parece rígido na primeira vez que acontece, normalmente no meio de uma edição. O motivo é prático. Uma variável sem uso muitas vezes é um bug: você calculou algo e depois usou o nome errado. Um import sem uso deixa todo build mais lento e esconde do que o arquivo realmente depende. O Go decidiu que valia barrar os dois na porta.
Quando você realmente precisa ignorar um valor, o identificador vazio _ diz isso de propósito. Você viu ele em for _, v := range x, onde o índice não interessa.
Lendo argumentos da linha de comando
Um programa Go lê as palavras digitadas depois do seu nome em os.Args, um slice de strings. os.Args[0] é o caminho do próprio programa, e os argumentos começam no índice 1.
Deixar a lógica numa função própria facilita testar sem digitar nada:
package main
import (
"fmt"
"os"
"strings"
)
func greeting(names []string) string {
if len(names) == 0 {
return "Hello, whoever you are"
}
return "Hello, " + strings.Join(names, " and ")
}
func main() {
fmt.Println(len(os.Args))
fmt.Println(greeting(os.Args[1:]))
fmt.Println(greeting([]string{"Ada", "Grace"}))
}
Rodando sem argumentos, imprime:
1
Hello, whoever you are
Hello, Ada and Grace
len(os.Args) é 1, porque a única entrada é o caminho do programa. os.Args[1:] é então um slice vazio, e greeting entra no primeiro caminho. A última linha chama greeting direto com dois nomes, que é como um teste chamaria.
Compile e passe argumentos de verdade, e o main tem alguém para cumprimentar:
$ go build
$ ./greet Ada Grace Linus
4
Hello, Ada and Grace and Linus
Hello, Ada and Grace
Agora os.Args guarda quatro strings: o caminho do programa e três nomes. O shell separou as palavras nos espaços antes de o Go ver qualquer coisa.
O go run também repassa argumentos. Tudo depois do pacote vai para o seu programa:
$ go run . Ada Grace
3
Hello, Ada and Grace
Hello, Ada and Grace
Para qualquer coisa além de algumas palavras simples, o pacote flag da biblioteca padrão faz o parse de opções como -port 8080 para você.
O que lembrar
- Um único comando
gocompila, roda, formata, faz o vet e gerencia dependências. Nenhum workspaceGOPATHé necessário, e um módulo pode ficar em qualquer pasta. - Um programa executável é
package maincom umafunc main(). Qualquer outro nome de pacote cria uma biblioteca. go runcompila e roda num passo só.go builddeixa um único binário que roda sem o Go instalado, eGOOS/GOARCHcompilam para outros sistemas.- O
go.modguarda o caminho do módulo e a linhago, e a linhagodecide quais recursos da linguagem o compilador permite. - Um nome que começa com letra maiúscula é exportado. Essa é a única regra de visibilidade.
- O
gofmtdá um layout só a todo código Go, e ogo vetpega código válido que quase certamente está errado, como um verbo doPrintfque não bate. - Variáveis e imports sem uso são erros de compilação. Use
_quando quiser ignorar um valor.
O Go coloca o build, a formatação e a primeira rodada de verificações numa ferramenta só, então todo projeto começa do mesmo lugar.