Go no Ubuntu: instalando e escrevendo seu primeiro programa

Mascote do LinuxPro digitando no laptop com o primeiro programa compilando no terminal, ao lado do gopher do Go

Nota (2026): Este post era, em 2017, a instalação do Go 1.8.3 com GOPATH — um jeito de trabalhar que a própria linguagem abandonou. Reescrevemos como um hello world de verdade no Ubuntu, com a versão atual e com módulos.

Este é o caminho mais curto entre um Ubuntu limpo e o seu primeiro programa em Go rodando. Leva uns dez minutos, e no fim você sai com um binário único que roda em qualquer Linux — sem interpretador, sem máquina virtual, sem dependência para instalar no servidor.

Por que Go, em duas frases

O Go foi desenhado no Google em 2007 porque o C++ demorava demais para compilar e a concorrência dava trabalho demais. O resultado: compila em segundos, tem concorrência embutida na linguagem e entrega um binário estático. É por isso que Docker, Kubernetes, Terraform e Prometheus são escritos nele. A história completa está em A história da linguagem Go.

Instalando no Ubuntu

Existem três caminhos. O tarball oficial é o recomendado — o pacote do apt costuma ficar uma ou duas versões atrás, e no Go isso importa porque a linguagem lança a cada seis meses.

# descobre a versão atual sem abrir o navegador
VER=$(curl -fsSL 'https://go.dev/VERSION?m=text' | head -n1)
echo "$VER"

# baixa e instala no lugar canônico
cd /tmp
curl -fLO "https://go.dev/dl/${VER}.linux-amd64.tar.gz"
sudo rm -rf /usr/local/go
sudo tar -C /usr/local -xzf "${VER}.linux-amd64.tar.gz"

Repare no rm -rf /usr/local/go antes de extrair: é o passo que a documentação oficial exige e que quase todo tutorial esquece. Extrair por cima de uma instalação antiga mistura arquivos de duas versões e gera erro difícil de diagnosticar.

Agora o PATH. Para valer no sistema inteiro e sobreviver a novos usuários:

echo 'export PATH=$PATH:/usr/local/go/bin' | sudo tee /etc/profile.d/go.sh
source /etc/profile.d/go.sh

go version

Se apareceu algo como go version go1.27.1 linux/amd64, está pronto. Se o comando não foi encontrado, o source acima resolve na sessão atual — nas próximas ele carrega sozinho.

Em máquina ARM, como um Raspberry Pi, troque linux-amd64 by linux-arm64.

As alternativas, e quando usá-las

# snap — versão recente, atualiza sozinho
sudo snap install go --classic

# apt — mais simples, versão mais velha
sudo apt install golang-go

The apt serve se você só precisa compilar algo de terceiro e não se importa com a versão. Para desenvolver, fique no tarball.

Hello, world

Antes de qualquer estrutura de projeto, veja a linguagem nua. Crie ola.go:

package main

import "fmt"

func main() {
    fmt.Println("Olá, mundo!")
}
go run ola.go

The go run compila e executa em memória, sem deixar binário — é o modo de experimentar. Três coisas para reparar: todo programa executável começa em package main; a função de entrada é func main(); e o import é obrigatório e não pode sobrar. Se você importar um pacote e não usar, o Go se recusa a compilar. É rígido de propósito.

Agora do jeito certo: um módulo

Na prática ninguém trabalha com arquivo solto. O Go organiza projeto em módulos desde a versão 1.11, e essa é a mudança que aposentou o velho GOPATH deste post em 2017:

mkdir ~/ola && cd ~/ola
go mod init exemplo.com/ola

Isso cria o go.mod, que declara o nome do módulo e a versão da linguagem. Agora um programa um pouco mais interessante — crie main.go:

package main

import (
    "bufio"
    "fmt"
    "os"
    "strings"
)

func main() {
    fmt.Print("Qual o seu nome? ")

    leitor := bufio.NewReader(os.Stdin)
    nome, err := leitor.ReadString('n')
    if err != nil {
        fmt.Fprintln(os.Stderr, "erro ao ler a entrada:", err)
        os.Exit(1)
    }

    nome = strings.TrimSpace(nome)
    if nome == "" {
        fmt.Println("Você não digitou nada.")
        return
    }

    fmt.Printf("Olá, %s! Bem-vindo ao Go.n", nome)
}
go run .

Aqui aparece a marca registrada da linguagem: o if err != nil. Em Go não existe exceção — toda função que pode falhar devolve o erro junto com o resultado, e você é obrigado a decidir o que fazer com ele. É verboso, é criticado, e é intencional: o caminho do erro fica visível no código, não escondido num catch distante.

Gerando o binário

Esta é a parte que vende a linguagem para quem administra servidor:

go build -o ola
ls -lh ola
./ola

O arquivo ola é um executável completo. Copie para outro Linux e ele roda — não é preciso instalar o Go lá, nem runtime, nem biblioteca. E dá para compilar para outro sistema sem sair do seu:

GOOS=linux   GOARCH=arm64 go build -o ola-arm      # Raspberry Pi
GOOS=windows GOARCH=amd64 go build -o ola.exe      # Windows
GOOS=darwin  GOARCH=arm64 go build -o ola-mac      # Mac com Apple Silicon

Compilação cruzada em uma variável de ambiente, sem toolchain extra. É por isso que tanta ferramenta de linha de comando moderna é distribuída como um binário por plataforma.

Usando uma dependência

go get github.com/fatih/color
package main

import "github.com/fatih/color"

func main() {
    color.Green("Olá, mundo!")
    color.New(color.FgYellow, color.Bold).Println("agora em amarelo e negrito")
}
go mod tidy    # ajusta o go.mod: adiciona o que falta, remove o que sobra
go run .

The go.sum, criado automaticamente, guarda o hash de cada dependência — é o que garante que a versão baixada amanhã é byte a byte a mesma de hoje. Versione os dois arquivos, go.mod and go.sum, junto com o código.

Um gostinho de concorrência

Não dá para apresentar Go sem mostrar a goroutine, que é o motivo de a linguagem ter ganhado a infraestrutura:

package main

import (
    "fmt"
    "net/http"
    "time"
)

func checar(url string, resultado chan<- string) {
    inicio := time.Now()
    resp, err := http.Get(url)
    if err != nil {
        resultado <- fmt.Sprintf("%-28s ERRO: %v", url, err)
        return
    }
    defer resp.Body.Close()
    resultado <- fmt.Sprintf("%-28s %s em %v", url, resp.Status, time.Since(inicio).Round(time.Millisecond))
}

func main() {
    sites := []string{
        "https://www.linuxpro.com.br",
        "https://go.dev",
        "https://kernel.org",
    }

    resultado := make(chan string)
    for _, s := range sites {
        go checar(s, resultado)     // dispara os três ao mesmo tempo
    }

    for range sites {
        fmt.Println(<-resultado)    // recebe conforme forem chegando
    }
}
go run .

Os três sites são consultados em paralelo, e o total demora o tempo do mais lento, não a soma dos três. Isso é uma palavra — go — e um canal. Sem biblioteca, sem pool de threads, sem callback.

As ferramentas que já vêm juntas

go fmt ./...      # formata no padrão oficial; não há debate de estilo em Go
go vet ./...      # aponta erro provável que compila mesmo assim
go test ./...     # roda os testes
go doc fmt.Println   # documentação sem sair do terminal
go clean -cache   # limpa o cache de build quando algo fica estranho

The go fmt merece destaque: a linguagem tem a estilo oficial, aplicado por ferramenta. Isso encerrou, de saída, a discussão sobre tabulação e chaves que consome tempo em outras comunidades.

Várias versões na mesma máquina

Quando um projeto está preso numa versão antiga, o próprio Go resolve — sem gerenciador externo:

go install golang.org/dl/go1.26@latest
go1.26 download
go1.26 version

E, desde a 1.21, existe algo ainda melhor: se o go.mod do projeto pedir uma versão que você não tem, o GOTOOLCHAIN baixa a correta na hora do build, sozinho.

Por onde seguir

Dez minutos, um binário estático e um gopher azul. Bom começo.