Hello, World e a toolchain: go run, go build e go mod sem mistério
Pré-requisitos: 01-bem-vindo-ao-go
Intuição
Pense na diferença entre cozinhar na sua casa e entregar comida congelada pronta. Quando você cozinha em casa, precisa da cozinha inteira: fogão, panelas, temperos, o fogo ligado durante todo o preparo. Quando entrega o prato congelado, a pessoa que recebe só precisa de um micro-ondas — todo o trabalho já foi feito antes, de uma vez, na sua cozinha.
Linguagens interpretadas como Python e JavaScript funcionam no primeiro modelo: para o seu programa rodar na máquina de outra pessoa, essa máquina precisa da “cozinha” — o interpretador, na versão certa, com as dependências instaladas. Quem já perdeu uma tarde com conflito de versão de Node ou virtualenv quebrado conhece o custo. Go funciona no segundo modelo: o comando go build faz todo o trabalho de tradução uma vez, na sua máquina, e o resultado é um arquivo binário único que roda em qualquer máquina do mesmo sistema — sem Go instalado, sem runtime, sem node_modules, sem nada além do próprio arquivo.
Essa é a razão de Docker e Kubernetes serem distribuídos como binários que você baixa e executa. E é a habilidade central desta aula: entender a toolchain — o conjunto de ferramentas que acompanha a linguagem — e o papel de cada comando. São três que você vai usar todos os dias: go run (compila e executa de uma vez, para desenvolvimento), go build (compila e guarda o binário, para distribuição) e go mod (declara a identidade e as dependências do projeto). Mais o gofmt da aula passada, que formata; e o go vet, que aponta código suspeito antes de virar bug.
A boa notícia para quem vem de ecossistemas com fadiga de ferramentas: tudo isso vem junto, em um único instalador oficial. Não existe decisão a tomar entre bundlers, transpilers e gerenciadores concorrentes. go é um comando só, com subcomandos — e nas próximas seções você vê exatamente o que cada um faz com o seu código.
Uma nota honesta sobre esta aula dentro do curso: aqui no navegador, o editor esconde a toolchain de você — os botões Executar e gofmt chamam o compilador e o formatador reais por baixo. Mas você está aprendendo Go para trabalhar com Go, e no trabalho a interface é o terminal. Esta aula é a ponte: quando você instalar Go na sua máquina (instrução de uma linha em go.dev/dl), cada comando daqui funciona idêntico.
Código anotado
Vamos escrever o programa que acompanha essa conversa: um “Hello, World” que se apresenta e mostra em qual sistema e arquitetura foi compilado — a mesma informação que os comandos da toolchain usam para gerar binários para cada plataforma.
O início você já conhece da aula passada: pacote executável e imports. A novidade é importar dois pacotes: a lista entre parênteses é a forma idiomática quando há mais de um. runtime expõe informações sobre o ambiente em que o programa está rodando.
package main
import (
"fmt"
"runtime"
)runtime.GOOS e runtime.GOARCH são constantes gravadas no momento da compilação: o sistema operacional alvo (linux, darwin, windows, js…) e a arquitetura de processador (amd64, arm64, wasm…). Elas não descobrem nada em tempo de execução — o compilador as fixa ao gerar o binário. Um binário compilado para Linux dirá linux para sempre, em qualquer lugar.
// platform descreve o alvo de compilação deste binário.
func platform() string {
return runtime.GOOS + "/" + runtime.GOARCH
}O main imprime a apresentação. Rode e repare na saída: aqui no curso ela será js/wasm, porque o compilador que roda no seu navegador gera WebAssembly. O mesmo programa, compilado no seu notebook, diria darwin/arm64 ou linux/amd64. Um código, muitos alvos — esse é o mecanismo por trás da cross-compilation que você vai exercitar adiante.
func main() {
fmt.Println("Hello, World!")
fmt.Println("Compilado para:", platform())
}Agora, o caminho que esse código percorre fora do navegador. Suponha o arquivo salvo como hello.go em um diretório vazio. O fluxo de trabalho real tem três passos, e cada um responde uma pergunta diferente.
Passo 1 — dar identidade ao projeto. Todo projeto Go moderno é um módulo: um diretório com um arquivo go.mod na raiz. O comando go mod init github.com/maria/hello cria esse arquivo com duas linhas: o caminho do módulo e a versão de Go. O caminho segue a convenção host/usuário/repositório porque ele é, ao mesmo tempo, o endereço de importação: se outro projeto quiser usar seu código, importa por esse nome. Para experimentos locais um nome curto funciona, mas o hábito da convenção não custa nada e evita renomear depois.
Passo 2 — rodar durante o desenvolvimento. go run hello.go compila o programa para um local temporário, executa e descarta o binário. É o ciclo rápido: editar, go run, olhar a saída, repetir. Nada fica para trás no diretório.
Passo 3 — construir para entregar. go build compila e grava o binário no diretório atual — hello (ou hello.exe no Windows). Esse arquivo é autônomo: copie-o para outra máquina do mesmo sistema/arquitetura e ele roda, sem Go instalado. A diferença entre go run e go build é só o destino do binário; a compilação é a mesma.
E o truque que fecha o raciocínio: as constantes GOOS e GOARCH que o programa imprime são configuráveis na compilação. GOOS=linux GOARCH=amd64 go build — rodado do seu Mac — produz um binário de Linux, pronto para o servidor. Nenhuma máquina virtual, nenhum toolchain extra: cross-compilation é parte do compilador padrão.
Experimente
Antes de rodar, preveja o que este programa imprime na segunda linha, sabendo que o compilador do curso gera WebAssembly para o navegador. Depois rode e confira.
package main
import (
"fmt"
"runtime"
)
func main() {
fmt.Println("Hello, World!")
fmt.Println("Compilado para:", runtime.GOOS+"/"+runtime.GOARCH)
fmt.Println("Versão da linguagem:", runtime.Version())
}Agora experimente: desalinhe a indentação de uma linha, adicione espaços a mais em volta do + e clique em gofmt — repare que ele arruma tudo sem perguntar nada. Depois remova o import de runtime deixando as chamadas, execute e leia o erro: o compilador aponta cada uso sem import. Por fim, deixe o import e remova os usos — o erro inverte para imported and not used. Esses dois erros vão aparecer no seu dia a dia; conhecê-los agora economiza minutos depois.
Exercício 1
Monte o caminho do módulo
Todo go mod init recebe um caminho de módulo, e a convenção do ecossistema é github.com/<usuário>/<repositório>. Implemente ModulePath(user string, repo string) string que monta esse caminho.
Entrada: o nome de usuário em user e o nome do repositório em repo, ambos não vazios e sem barras. Saída: a string "github.com/<user>/<repo>".
Exemplos:
ModulePath("maria", "todo-cli")→"github.com/maria/todo-cli";ModulePath("golang-br", "curso")→"github.com/golang-br/curso".
Solução & Explicação
Três partes fixas, duas barras, concatenação direta:
func ModulePath(user string, repo string) string {
return "github.com/" + user + "/" + repo
}Custo O(n) no tamanho do resultado, sem casos de borda porque o contrato garante entradas limpas. A alternativa fmt.Sprintf("github.com/%s/%s", user, repo) produz a mesma string; para duas substituições em um template curto, o + deixa cada pedaço literal visível e dispensa a interpretação da string de formato. A armadilha é puramente de atenção: esquecer a barra do meio produz github.com/mariatodo-cli, que parece certo de relance — e o painel de testes mostra a diferença na comparação esperado × recebido. Vale registrar por que esse formato importa: o caminho do módulo é o nome que outros projetos usarão em import, então errar aqui significa quebrar todo mundo que depende de você.
Exercício 2
Gere o comando de cross-compilation
Sua equipe compila o mesmo serviço para vários alvos, e você quer gerar os comandos a partir de uma configuração. Implemente BuildCommand(goos string, goarch string, output string) string que monta a linha de comando de cross-compilation.
Entrada: o sistema alvo em goos, a arquitetura em goarch (ambos não vazios) e o nome do binário em output, que pode ser vazio. Saída: a string "GOOS=<goos> GOARCH=<goarch> go build -o <output>" — ou, quando output é vazio, apenas "GOOS=<goos> GOARCH=<goarch> go build", sem -o e sem espaço sobrando no final.
Exemplos:
BuildCommand("linux", "amd64", "servidor")→"GOOS=linux GOARCH=amd64 go build -o servidor";BuildCommand("windows", "arm64", "")→"GOOS=windows GOARCH=arm64 go build".
A novidade em relação ao exercício 1 é a decisão: parte do comando só existe em um dos casos. Cuidado com o espaço antes de -o — ele pertence ao sufixo, não ao prefixo.
Solução & Explicação
Monte o prefixo comum uma vez; decida o sufixo depois:
func BuildCommand(goos string, goarch string, output string) string {
command := fmt.Sprintf("GOOS=%s GOARCH=%s go build", goos, goarch)
if output == "" {
return command
}
return command + " -o " + output
}O retorno antecipado no caso vazio é o padrão que você verá em todo código Go: trate o caso simples e saia, deixando o caminho principal sem indentação extra. Custo O(n) no tamanho do comando.
A alternativa que parece equivalente é escrever dois Sprintf completos, um em cada ramo do if. Funciona hoje — e é exatamente por isso que é armadilha: as duas strings quase iguais são duplicação, e a primeira mudança futura (digamos, adicionar -trimpath) será feita em um ramo e esquecida no outro. Testes só pegam isso se alguém lembrar de testar os dois ramos de novo. Outra falha comum é colar o espaço no lugar errado: command + "-o " produz build-o servidor. Quando uma string é montada por partes, cada separador precisa de dono definido — aqui, o espaço pertence ao sufixo " -o ".
Exercício 3
Reproduza a saída de go version
go version é o primeiro comando que você roda para diagnosticar qualquer ambiente Go — e a primeira coisa que vão te pedir em qualquer issue de bug. Implemente VersionLine(major int, minor int, patch int, goos string, goarch string) string que reproduz exatamente essa saída.
Entrada: os três componentes numéricos da versão e o par sistema/arquitetura. Saída: a string "go version go<major>.<minor>.<patch> <goos>/<goarch>".
Exemplos:
VersionLine(1, 26, 1, "darwin", "arm64")→"go version go1.26.1 darwin/arm64";VersionLine(1, 26, 1, "js", "wasm")→"go version go1.26.1 js/wasm"— o ambiente exato onde seu código roda neste curso.
Este exercício combina as duas aulas: números dentro de string exigem a técnica do exercício 2 da aula 1. Repare no go minúsculo colado na versão — go1.26.1, sem espaço.
Solução & Explicação
Cinco valores, um template, uma linha:
func VersionLine(major int, minor int, patch int, goos string, goarch string) string {
return fmt.Sprintf("go version go%d.%d.%d %s/%s", major, minor, patch, goos, goarch)
}A força do Sprintf aparece exatamente neste formato denso: o template "go version go%d.%d.%d %s/%s" mostra a frase inteira com pontos, espaços e barra nas posições exatas. Custo O(n) no tamanho da linha.
A alternativa por concatenação exige strconv.Itoa para cada inteiro — lembre da aula 1: Go não converte int em string implicitamente — e o resultado são sete pedaços costurados com seis +, onde um ponto trocado por espaço passa despercebido no olho e reprova no teste. Quando o formato tem mais de dois ou três buracos, o template ganha. A pegadinha específica deste formato é o go duplicado: a saída real tem go version e o prefixo go colado na versão. Quem “corrige” o que parece redundância devolve go version 1.26.1 ... e falha os três casos — reproduzir uma saída existente significa reproduzi-la, não melhorá-la.
Resumo
go runcompila, executa e descarta — o ciclo de desenvolvimento.go buildcompila e grava um binário autônomo que roda sem Go instalado — o ciclo de entrega.go mod init <caminho>cria ogo.mod, que dá identidade ao projeto; a convençãogithub.com/usuário/repofaz o caminho do módulo ser também o endereço de importação.runtime.GOOSeruntime.GOARCHsão fixadas na compilação, e as variáveisGOOS/GOARCHnogo buildfazem cross-compilation nativa — um Mac gera binário de Linux sem ferramenta extra.- Import não usado e identificador sem import são erros de compilação — você viu os dois no Experimente e vai revê-los a vida inteira.
- A toolchain inteira vem no instalador oficial e dispensa configuração: essa uniformidade é parte do design da linguagem, não acidente.
Na próxima aula, imersao/03-valores-e-tipos, você sai das ferramentas e entra na linguagem: os tipos básicos de Go, o que a tipagem estática garante e por que int e string não se misturam sem permissão explícita — algo que você já sentiu nos exercícios de hoje.