Introdução ao Go: história, filosofia e os criadores da linguagem

Introdução ao Go: história, filosofia e os criadores da linguagem

A origem do Go: o build de 45 minutos que motivou Griesemer, Pike e Thompson, a linha do tempo de 2007 ao Go 1.23, as escolhas de projeto por trás do gofmt, do erro como valor e das 25 palavras-chave, e onde a linguagem rende mais — e menos — que as alternativas.
Golang

• • 14 min de leitura

Talvez você se pergunte: por que mais uma linguagem de programação? No final dos anos 2000, os engenheiros do Google enfrentavam um problema concreto. Os sistemas da empresa cresciam em escala e complexidade a uma velocidade que as ferramentas disponíveis não acompanhavam com conforto. C++ oferecia desempenho, mas seus tempos de compilação eram dolorosamente lentos e sua complexidade sintática exigia esforço considerável da equipe. Java resolvia parte da produtividade, mas carregava o peso de uma máquina virtual e de um ecossistema verboso. Python era ágil para escrever, porém insuficiente para sistemas de alto desempenho e concorrência massiva.

Foi nesse cenário que três engenheiros experientes decidiram projetar algo novo do zero — não por vaidade intelectual, mas por necessidade prática.

Os criadores

Robert Griesemer é engenheiro de compiladores com décadas de experiência. Trabalhou no V8, o motor JavaScript do Chrome, e na máquina virtual HotSpot do Java. Sua contribuição para Go está profundamente ligada ao design do compilador e ao sistema de tipos da linguagem.

Rob Pike é um veterano da computação. Trabalhou nos laboratórios Bell, onde participou do desenvolvimento do sistema operacional Plan 9 e da codificação UTF-8, ao lado de Ken Thompson. É também co-autor do livro clássico The Unix Programming Environment. Em Go, Pike influenciou fortemente a filosofia de simplicidade e a abordagem à concorrência.

Ken Thompson dispensa apresentações no mundo da computação. Criador do Unix, co-inventor da linguagem B (predecessora do C) e co-desenvolvedor do UTF-8, Thompson trouxe para o Go décadas de sabedoria sobre o que faz um sistema ser duradouro e eficiente. Sua participação no projeto foi fundamental nas fases iniciais de design.

Os três iniciaram as discussões sobre a linguagem em setembro de 2007, enquanto aguardavam a compilação de um sistema em C++ que levava cerca de 45 minutos. A lenda conta que foi justamente nessa espera improdutiva que a conversa sobre "como uma linguagem ideal seria" se tornou um projeto concreto.

A linha do tempo

  • Setembro de 2007 — Griesemer, Pike e Thompson iniciam o design da linguagem internamente no Google.
  • Novembro de 2009 — Go é anunciada publicamente como um projeto open source. A data, 10/11/2009, é considerada o nascimento oficial da linguagem.
  • Março de 2012 — Lançamento do Go 1.0, com a promessa de estabilidade de API que persiste até hoje. Programas escritos para Go 1.0 compilam e funcionam corretamente nas versões mais recentes.
  • Agosto de 2015 — O Go 1.5 passa a ter o compilador escrito no próprio Go e troca o garbage collector por um coletor concorrente, que trabalha junto com o programa em vez de pará-lo inteiro. As pausas caem para poucos milissegundos; com o Go 1.8, em 2017, ficam abaixo de um milissegundo na maior parte dos casos.
  • Março de 2022 — O Go 1.18 traz Generics, a adição mais significativa à linguagem em anos, respondendo a uma das demandas mais antigas da comunidade.
  • 2023 e 2024 — O Go 1.21 traz o slog, de logging estruturado; o 1.22 dá a cada volta do for a sua própria variável; o 1.23 permite percorrer com range uma função iteradora. As duas últimas só valem para módulos cujo go.mod declara a versão correspondente — com go 1.22 no arquivo, o range sobre função é recusado com requires go1.23 or later. Há anos o ritmo é de uma versão nova a cada seis meses, em fevereiro e em agosto.

A filosofia da linguagem

Go foi desenhada com um conjunto de princípios muito deliberados, e entendê-los é tão importante quanto aprender a sintaxe.

Simplicidade acima de tudo. A linguagem tem poucas palavras-chave — apenas 25. Não existe sobrecarga de operadores, não existe herança de classes, não existe manipulação de exceções com try/catch. Cada decisão de design foi guiada pela pergunta: isso é realmente necessário?

Compilação rápida. Um dos objetivos originais era que grandes bases de código compilassem em segundos, não minutos. Isso impacta diretamente a produtividade no dia a dia.

Concorrência como cidadã de primeira classe. Go foi projetada para o mundo multicore. Goroutines e channels são parte da linguagem, não bibliotecas externas. Rodar milhares de tarefas concorrentes é natural em Go.

Um único jeito de formatar o código. A ferramenta gofmt formata qualquer código Go de forma padronizada. Não existem guerras de tabs versus espaços em times que usam Go — o formatador decide, e todos aceitam.

Erros são valores. Go não usa exceções. Funções retornam o resultado e um valor de erro. Para usar o resultado, o programador precisa receber também o erro e decidir, de forma visível no código, o que fazer com ele — embora nada impeça de descartá-lo, e é para isso que linters como o errcheck existem. Isso parece verboso no início, mas produz código mais previsível e auditável.

Um primeiro programa

O melhor jeito de sentir uma linguagem é lê-la. Abaixo está o programa mais clássico possível, com alguns elementos extras para ilustrar a estrutura.

package main

import "fmt"

func main() {
    fmt.Println("Olá, mundo. Bem-vindo ao Go.")
}

Algumas observações importantes sobre esse código mínimo:

  • Todo programa executável em Go pertence ao pacote main.
  • A função main é o ponto de entrada — sem ela, o compilador recusa o programa.
  • import "fmt" traz o pacote de formatação da biblioteca padrão. Diferente de outras linguagens, Go não permite importações que não sejam usadas: isso causa erro de compilação.
  • Não há ponto e vírgula ao final das linhas — o compilador os insere automaticamente.

Go na prática: onde a linguagem é usada

Conhecer os casos de uso reais ajuda a compreender para onde os estudos devem apontar.

Infraestrutura e DevOps. Docker, Kubernetes, Terraform, Prometheus e o próprio CLI do GitHub foram escritos em Go. A linguagem tornou-se o padrão de fato para ferramentas de infraestrutura cloud.

Backends de alta performance. Empresas como Uber, Twitch, Cloudflare, Dropbox e o próprio Google usam Go em serviços que precisam responder a milhões de requisições com baixa latência.

Ferramentas de linha de comando. A capacidade de compilar um binário único — estático de fato quando compilado com CGO_ENABLED=0 — torna Go ideal para CLIs distribuídas.

APIs e microserviços. Frameworks como Gin, Echo e Fiber tornaram Go uma escolha popular para times que constroem APIs RESTful performáticas.

Go versus outras linguagens: uma perspectiva honesta

Go não é a linguagem certa para tudo, e seus criadores nunca afirmaram o contrário.

Para sistemas de tempo real extremo, onde cada microssegundo importa, C e Rust ainda levam vantagem pela ausência de garbage collector. Para análise de dados e machine learning, Python domina pelo ecossistema. Para aplicações corporativas com regras de negócio complexas, Java e Kotlin têm ecossistemas mais maduros.

O ponto forte de Go está na interseção entre produtividade de desenvolvimento e performance em produção. Equipes que usam Go reportam com frequência que conseguem escrever código correto e eficiente mais rápido do que em linguagens de sistemas tradicionais, sem abrir mão de desempenho comparável.

Go nasceu de incômodos concretos — um build de 45 minutos, concorrência difícil de acertar, código que só o próprio autor entendia —, e quase toda regra da linguagem responde a um deles: o gofmt sem opções, o import que não pode sobrar, o erro que volta como valor. Quando alguma dessas regras parecer rígida demais nas próximas semanas, vale procurar o incômodo por trás dela antes de tentar contorná-la; em geral ele custou caro a alguém, em escala.

Fontes e leituras recomendadas

Exercícios

Exercício 1

Numa conversa sobre qual linguagem usar num serviço sensível a latência, alguém descarta Go de saída: "tem garbage collector, então o programa congela de tempos em tempos". O argumento valia para o Go de 2012? E para o de hoje?

Ver resposta

✓ Resposta: Para o Go de 2012, valia em boa parte: o coletor das primeiras versões parava o programa inteiro para marcar a memória, e as pausas cresciam com o tamanho do heap. A virada veio com o Go 1.5, em 2015, que trocou esse modelo por um coletor concorrente, que faz a maior parte do trabalho enquanto as goroutines continuam rodando e só para o mundo por instantes curtos. Com o Go 1.8, em 2017, essas pausas passaram a ficar abaixo de um milissegundo na maior parte dos casos. Hoje o argumento precisa ser mais preciso: o custo do coletor não sumiu, ele mudou de lugar, e aparece como uso de CPU e memória extra, não como congelamento. Para quase todo serviço web isso é irrelevante diante da latência de rede e de banco. Onde cada microssegundo conta de forma rígida, como controle de hardware ou áudio em tempo real, C, C++ e Rust continuam sendo a escolha, como o próprio artigo reconhece. E quem precisa domar o coletor tem duas alavancas na própria linguagem: GOGC, que regula a frequência das coletas, e GOMEMLIMIT, que chegou no Go 1.19 e fixa um teto de memória.

Exercício 2

O artigo conta que Go nasceu durante a espera por um build de C++ de 45 minutos. Um colega pergunta o que, no desenho da linguagem, faz o compilador do Go ser rápido. Cite duas regras que um programador sente no dia a dia e que existem, em boa parte, por causa disso.

Ver resposta

✓ Resposta: A primeira é que import sem uso é erro. Em C++, um #include esquecido continua sendo lido e processado em toda compilação, e os cabeçalhos puxam outros cabeçalhos em cascata; em Go, o compilador recusa o programa com "os" imported and not used, o que mantém o grafo de dependências do tamanho do que o código realmente usa. A segunda é que import circular é proibido: se o pacote a importa b e b importa a, o build para com import cycle not allowed e mostra a cadeia inteira. Sem ciclos, os pacotes formam uma árvore que pode ser compilada de baixo para cima, cada um uma vez só, e em paralelo quando são independentes. Ajuda também que não existem arquivos de cabeçalho: ao compilar um pacote, o compilador lê um resumo do que os pacotes importados exportam, em vez de reprocessar o código-fonte deles. As duas regras irritam no começo, e a do ciclo costuma aparecer justamente quando o desenho dos pacotes está confuso, o que faz dela também um aviso de arquitetura.

Exercício 3

Para mostrar como Go é pequena, um colega lista as 25 palavras-chave num slide e inclui int, string, true, nil, len e append. O que está errado na lista, e que consequência prática, pouco conhecida, isso tem?

Ver resposta

✓ Resposta: Nenhum desses seis é palavra-chave. As 25 são 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. Os tipos básicos, as constantes true, false e nil e as funções embutidas como len e append são identificadores pré-declarados, que vivem num escopo que envolve todos os pacotes. A consequência é que eles podem ser redeclarados: len := 3 e true := false compilam sem erro no Go 1.27, e o go vet também não reclama. A partir dessa linha, naquele escopo, len(s) deixa de funcionar e true vale false. Ninguém faz isso de propósito com true, mas variáveis chamadas len, new, copy ou string aparecem em código real e produzem erros confusos algumas linhas abaixo. Linters como o predeclared, disponível no golangci-lint, existem para pegar exatamente isso.

Exercício 4

Um time vindo do Java lê que "erros são valores" e conclui que, em Go, é impossível esquecer de tratar um erro. Numa revisão, você encontra a linha strconv.Atoi(entrada) sozinha, sem nada à esquerda. Ela compila? E se a linha fosse n := strconv.Atoi(entrada)?

Ver resposta

✓ Resposta: A primeira compila, e é aí que a conclusão do time falha. Uma chamada de função usada como instrução pode descartar todos os valores de retorno, erro incluído: strconv.Atoi("abc") sozinho passa pelo compilador e pelo go vet sem nenhum aviso, e a conversão que falhou some em silêncio. A segunda não compila: assignment mismatch: 1 variable but strconv.Atoi returns 2 values. Ou seja, Go obriga você a receber o erro quando quer o resultado, e n, _ := strconv.Atoi(entrada) deixa o descarte escrito e visível na revisão. Mas nada obriga a olhar o erro de uma função cujo resultado você não quer, e isso é comum com Close, Write e os.Remove. Para esses casos existe o linter errcheck, que vem habilitado por padrão no golangci-lint e aponta toda chamada que devolve error e cujo retorno ninguém examina. O ganho real do modelo de Go é que o caminho do erro fica no código, à vista, e não que seja impossível errar.

Exercício 5

Uma equipe desenvolve em Linux uma ferramenta de linha de comando que precisa ser distribuída para colegas em Windows e em Macs com processador Apple. Ela vai precisar de três máquinas de build? O que muda se a ferramenta usar uma biblioteca em C via cgo?

Ver resposta

✓ Resposta: Não precisa. O compilador do Go gera código para outros sistemas e arquiteturas a partir da mesma máquina, bastando duas variáveis de ambiente: GOOS=windows GOARCH=amd64 go build -o ferramenta.exe . produz um executável do Windows (o arquivo começa com os bytes MZ, a assinatura do formato), e GOOS=darwin GOARCH=arm64 go build gera o binário para Macs com Apple Silicon, tudo num Linux e sem instalar mais nada. O go tool dist list do Go 1.27 lista 47 combinações de sistema e arquitetura. A situação muda com cgo. Ao compilar para outra plataforma, o cgo vem desligado por padrão, e um pacote que depende dele deixa de compilar ou perde a parte em C. Para mantê-lo, é preciso um compilador C que gere código para o sistema de destino, o que em geral significa montar uma toolchain cruzada ou compilar em cada plataforma, normalmente no CI. Por isso projetos que querem distribuição simples preferem bibliotecas escritas só em Go, como os drivers de SQLite sem cgo.

Comentários

Mais em Golang

Estruturas de controle: if, for e switch
Estruturas de controle: if, for e switch

Controle de fluxo em Go: if com inicialização, as formas do for, incluindo o…

Ponteiros: conceito, uso e quando evitar
Ponteiros: conceito, uso e quando evitar

Ponteiros em Go: os operadores & e *, o nil que entra em pânico e o que não…

Consumindo APIs externas com http.Client
Consumindo APIs externas com http.Client

Consumindo APIs em Go: http.Client com timeout, o Transport que perde o proxy…