O que é TinyGo e por que ele existe
O compilador oficial do Go (gc) é excelente — mas foi desenhado para servidores. Ele gera binários robustos, com garbage collector completo, goroutines preemptivas e suporte total à biblioteca padrão. Problema: um “hello world” padrão pesa 837 KB stripped. Num Arduino Uno com 32 KB de flash total, isso é impossível.
O TinyGo resolve isso usando o LLVM como backend (a mesma infraestrutura do Clang e do Rust) em vez do compilador nativo do Go. Com otimizações agressivas de tamanho e um modelo de runtime mais leve, ele consegue gerar binários que cabem em ambientes extremos.
| Característica | Go Padrão | TinyGo |
|---|---|---|
| Backend | Custom SSA | LLVM |
| Hello world (stripped) | ~837 KB | ~10 KB |
| Goroutines | Preemptivas | Cooperativas |
| Garbage collector | Mark-sweep completo | Conservador, plugável |
| Targets suportados | Linux/macOS/Windows/WASM | 100+ placas + WASM |
| Stdlib | Completa | Parcial |
As goroutines cooperativas merecem atenção especial — voltamos a isso em breve.
Embarcados: Go num microcontrolador de verdade
As placas suportadas
O TinyGo suporta mais de 100 placas. Não é uma lista de nicho:
- Raspberry Pi Pico e Pico 2 (RP2040 e RP2350) — suporte excelente, incluindo multicore
- Arduino — Uno, Nano, Mega, MKR WiFi 1010, Nano 33 BLE/IoT
- Adafruit — Circuit Playground, Feather series, ItsyBitsy, PyBadge
- Nordic nRF52840 — Particle Xenon, BBC micro:bit v2, Feather nRF52840
- STM32 — Nucleo F103RB, L432KC, Bluepill, Discovery
- ESP32, ESP32-C3, ESP32-S3 — suporte existe, mas com asterisco importante
- Seeed XIAO, Wio Terminal, Silicon Labs, SparkFun e mais
O asterisco do ESP32: o WiFi e o Bluetooth não funcionam no TinyGo. O stack de rádio da Espressif depende de SDK proprietário que o TinyGo ainda não integrou. Para projetos ESP32 com conectividade, por enquanto, você ainda precisa do ESP-IDF ou Arduino framework.
A API de hardware: o pacote machine
O segredo da portabilidade é o pacote machine — a camada de abstração de hardware do TinyGo. Ele expõe GPIO, I2C, SPI, UART, ADC e PWM numa interface unificada. O mesmo código de leitura I2C roda no Arduino Uno, no Raspberry Pi Pico e no nRF52840 sem modificação.
Piscar um LED em qualquer placa suportada:
package main
import (
"machine"
"time"
)
func main() {
led := machine.LED
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
for {
led.Low()
time.Sleep(time.Millisecond * 500)
led.High()
time.Sleep(time.Millisecond * 500)
}
}
Para gravar num Arduino:
tinygo flash -target=arduino main.go
Lendo temperatura via I2C com um sensor BMP180:
package main
import (
"time"
"machine"
"tinygo.org/x/drivers/bmp180"
)
func main() {
machine.I2C0.Configure(machine.I2CConfig{})
sensor := bmp180.New(machine.I2C0)
sensor.Configure()
if !sensor.Connected() {
println("BMP180 não encontrado")
return
}
for {
temp, _ := sensor.ReadTemperature()
println("Temperatura:", float32(temp)/1000, "°C")
pressure, _ := sensor.ReadPressure()
println("Pressão:", float32(pressure)/100000, "hPa")
time.Sleep(2 * time.Second)
}
}
O ecossistema de drivers (tinygo.org/x/drivers) tem mais de 130 drivers — sensores de temperatura, barômetros, acelerômetros, displays OLED, módulos LoRa, co-processadores WiFi. Não é tão vasto quanto o Arduino, mas cobre a maioria dos cenários práticos.
O caso do Arduino Uno: 32 KB e uma piada pesada
O ATmega328P do Arduino Uno tem 32 KB de flash e 2 KB de RAM. O pacote fmt do Go sozinho pode estourar isso. Por padrão, o TinyGo desabilita goroutines no AVR (arquitetura do Uno) por questões de memória estática.
Um hello world compilado para AVR: ~1.174 bytes de flash. Cabe. E aí a piada se inverte — você está rodando Go num chip de 8 bits, em 1 KB, e funciona.
Claro que tem limites sérios. Se o seu projeto exige fmt.Printf formatado, múltiplas goroutines e 50 dependências, o Arduino Uno não é o lugar. Mas para controle de pinos, leitura de sensores básicos e lógica simples? TinyGo entrega.
A questão das goroutines: cooperativo ≠ preemptivo
Aqui mora o maior gotcha do TinyGo para quem vem do Go padrão.
No Go convencional, goroutines são preemptivas — o scheduler pode interrompê-las a qualquer momento. No TinyGo, o scheduler é cooperativo: uma goroutine só cede o controle quando executa uma operação de IO, time.Sleep, canal ou chama runtime.Gosched() explicitamente.
Na prática, isso significa:
// PROBLEMA: essa goroutine nunca cede o controle
// e bloqueia todas as outras
go func() {
for {
processarDados() // CPU pura, sem IO, sem sleep
}
}()
// CORRETO: adicione um ponto de yield
go func() {
for {
processarDados()
runtime.Gosched() // cede para outros goroutines
}
}()
É cooperativo porque implementa goroutines usando corrotinas LLVM — a mesma mecânica que o Clang usa para C++ coroutines. A vantagem é o footprint mínimo; a desvantagem é que o programador precisa ser mais consciente dos pontos de yield.
Para a maioria dos projetos embarcados isso não é problema real — você já está dormindo entre leituras de sensor, esperando por eventos, lidando com IO. O pattern natural já é cooperativo.
WebAssembly: o outro superpoder
Enquanto o mundo embarcado é o caso de uso histórico, o TinyGo está ganhando tração crescente em WebAssembly — e por uma razão muito prática: tamanho de binário.
O problema do WASM com Go padrão
O Go padrão compila para WASM, mas o runtime mínimo carrega muita coisa junto. Um “hello world” gera facilmente 2 MB ou mais de arquivo .wasm. Para funções serverless em edge computing — onde você paga por execução e latência de cold start importa — isso é um problema real.
O TinyGo resolve:
| Estágio de otimização | Tamanho |
|---|---|
| Go padrão (WASM) | 2 MB+ |
| TinyGo (sem flags) | 1,1 MB |
TinyGo com -no-debug | 396 KB |
TinyGo com wasm-opt -O | 377 KB |
TinyGo com -gc=leaking + -no-debug | < 100 KB |
A flag -no-debug remove símbolos de debug — que chegam a representar 2/3 do binário. Para funções de produção, você não precisa deles.
Compilando para WASM
# Para o browser (requer wasm_exec.js do TinyGo, não do Go padrão)
GOOS=js GOARCH=wasm tinygo build -o app.wasm ./main.go
# Para WASI (runtimes server-side como Fermyon, wazero, Fastly)
tinygo build -o app.wasm -target=wasip1 ./main.go
# Máxima otimização de tamanho para produção
tinygo build -o app.wasm -target=wasi -gc=leaking -no-debug ./main.go
Atenção: o arquivo wasm_exec.js do TinyGo não é o mesmo que o do Go padrão. Use o que vem na instalação do TinyGo.
Exportando funções para o host
A diretiva //go:wasmexport (padronizada nas versões recentes) exporta funções Go diretamente para o JavaScript ou runtime WASI:
package main
//go:wasmexport multiply
func multiply(x, y int32) int32 {
return x * y
}
func main() {}
No JavaScript:
const result = wasm.exports.multiply(5, 3); // retorna 15
Edge computing: onde o TinyGo brilha
Plataformas como Fermyon Spin, Fastly Compute e wazero já suportam TinyGo WASM nativamente. Para funções que precisam iniciar rápido e consumir poucos recursos, o TinyGo é frequentemente a melhor escolha entre as linguagens Go-familiares.
Performance: TinyGo vs C vs MicroPython vs Rust
Um estudo publicado na revista Electronics (MDPI, 2023) testou Go, C/C++, MicroPython e Rust num ESP32, rodando algoritmos reais: FFT, CRC-32, SHA, filtros IIR e FIR.
Os resultados principais:
- C/C++ é mais rápido na maioria dos testes — não surpreendente
- TinyGo fica próximo a Rust, os dois significativamente acima do MicroPython
- MicroPython é 100x a 1000x mais lento que as linguagens compiladas nas operações pesadas de CPU
- TinyGo tem variância de execução zero — desvio padrão do tempo de execução = 0
O último ponto é o mais interessante e contraintuitivo. C e Rust mostram variabilidade nos tempos de execução. O TinyGo não. Para sistemas de tempo real onde a consistência importa mais que a velocidade bruta — certos protocolos time-triggered, por exemplo — isso é uma vantagem genuína sobre C.
A conclusão do estudo: “C/C++, TinyGo e Rust são adequados para aplicações de tempo crítico. MicroPython é apropriado apenas para prototipagem ou tarefas não sensíveis ao tempo.”
Estado atual em 2026
A versão mais recente é a v0.40.1 (dezembro de 2024), com LLVM 20, GC 10% mais rápido e suporte ao Seeed XIAO ESP32-S3.
O milestone mais significativo dos últimos 12 meses foi o suporte multicore no RP2040 e RP2350, anunciado no FOSDEM 2025. A palestra “Implementing parallelism: how we added threading and multicore support in TinyGo” explicou como os dois núcleos Cortex-M0+ do Pico agora podem ser usados simultaneamente — uma mudança arquitetural significativa que o projeto trabalhou por anos.
Para o WASM, a formalização do //go:wasmexport e o suporte ao WASI Preview 2 (wasip2) abriram compatibilidade com a próxima geração de runtimes serverless.
Limitações reais (sem romantismo)
Não existe almoço grátis. O TinyGo tem limitações que importam:
Na linguagem:
reflecttem implementação parcial. Muitos pacotes da stdlib que dependem de reflection não compilam.recovernão funciona em WebAssembly — panics não podem ser capturados.cgotem suporte parcial.- Maps funcionam, mas com chaves de struct podem usar hashing via reflection (mais lento).
No hardware:
- ESP32 WiFi/Bluetooth: não suportado. Se você precisa de conectividade, esse ainda é um bloqueador sério.
- Arduino Uno (AVR): sem goroutines por padrão, sem
fmt, limitado a programas básicos. - O nível de suporte varia muito entre placas. RP2040, SAMD51 e nRF52840 são battle-tested; outros são experimentais.
No ecossistema:
- Qualquer pacote Go que puxa
net/http,database/sqlou dependências pesadas não compila. Você vive num subconjunto de Go. - O ecossistema de drivers é bom, mas menor que o Arduino/C.
Se você está avaliando TinyGo para um projeto, a primeira pergunta é: “minha plataforma tem suporte estável?” A segunda: “preciso de WiFi?” Se as duas respostas forem sim, espere um pouco mais.
Por que isso importa para equipes de software
Há uma questão de organização de times que raramente aparece nas discussões técnicas.
Hoje, um backend Go e o firmware do hardware que ele controla são projetos completamente distintos — ecosistemas diferentes, ferramentas diferentes, e geralmente times diferentes. O engenheiro de backend que escreve a API não entende o firmware. O engenheiro de embedded que escreve o firmware não faz ideia do que acontece no servidor.
O TinyGo não elimina essa divisão, mas reduz o custo dela. Um desenvolvedor Go experiente pode ler, entender e eventualmente contribuir com firmware TinyGo sem aprender C++, Rust ou MicroPython do zero. A mesma função de CRC-32 pode ser testada no servidor e deployada no dispositivo. O mesmo desenvolvedor pode escrever a lógica de processamento e o módulo WASM que roda no edge.
Isso não é “escreva uma vez, rode em todo lugar” no sentido ingênuo — as limitações são reais. Mas é a primeira vez que uma linguagem mainstream faz essa ponte de forma prática, e isso muda como times podem ser estruturados.
O C vai continuar dominando embedded por muitos anos. O TinyGo não precisa substituí-lo para ser útil. Só precisa abrir a porta para os milhões de desenvolvedores Go que nunca tocaram num microcontrolador — e, aos poucos, está conseguindo.
Referência: TinyGo.org | GitHub | Estudo ESP32 — MDPI | Fermyon Spin + TinyGo | FOSDEM 2025












