Cheatsheet Go
Linguagem de programação do Google
Go
Sintaxe Básica
Variáveis
package main
import "fmt"
func main() {
var nome string = "Ana"
var idade int = 30
var ativo bool = true
var x, y int = 1, 2
fmt.Println(nome, idade, ativo, x, y)
}Declara variáveis com var, indicando o tipo ou deixando o Go inferi-lo. Podes declarar várias na mesma linha. O valor inicial, se omitido, é o zero value do tipo.
Operadores
// Aritméticos + - * / % // Comparação == != > < >= <= // Lógicos && || ! // Bitwise & | ^ << >> // Endereço e dereferência &x *p
Go tem operadores aritméticos, de comparação, lógicos (&&, ||, !) e bitwise. % dá o resto da divisão. & obtém o endereço de uma variável e * desreferencia um ponteiro.
Conversões de tipo
var f float64 = 3.99 var i int = int(f) // 3 (trunca) var n int = 65 var c string = string(n) // "A" (rune) // Não há conversão implícita var x int = 10 // var y float64 = x // ERRO var y float64 = float64(x) // OK
Go não faz conversões automáticas entre tipos: é preciso converter explicitamente com int(), float64(), etc. Converter float64 para int trunca a parte decimal.
Inferência com :=
func main() {
preco := 9.99 // float64
nome := "Ana" // string
contador := 0 // int
ok := true // bool
// várias de uma vez
a, b := 1, 2
}O operador := declara e infere o tipo a partir do valor, mas só funciona dentro de funções. É a forma mais comum de criar variáveis locais. Ao nível do pacote usa var.
Zero values
var i int // 0 var f float64 // 0.0 var s string // "" var b bool // false var p *int // nil var sl []int // nil var m map[string]int // nil
Toda variável em Go tem um valor inicial automático, o zero value: 0 para números, string vazia para string, false para bool e nil para ponteiros, slices e maps. Não há variáveis não inicializadas.
strconv
import "strconv"
// int -> string
s := strconv.Itoa(42) // "42"
// string -> int
n, err := strconv.Atoi("123") // 123
// outros
strconv.FormatFloat(3.14, 'f', 2, 64) // "3.14"
strconv.ParseFloat("3.14", 64)
strconv.FormatBool(true) // "true"
strconv.ParseBool("true")O pacote strconv converte entre strings e tipos básicos. Itoa() transforma int em string e Atoi() faz o inverso, devolvendo também um error se o texto não for válido.
Tipos de dados
// Inteiros int, int8, int16, int32, int64 uint, uint8, uint16, uint32, uint64 // Números com vírgula float32, float64 // Outros string // texto (imutável) bool // true / false byte // alias de uint8 rune // alias de int32 (Unicode) complex64, complex128
Go é fortemente tipado. Os inteiros têm tamanhos fixos (int8 a int64); int depende da arquitetura. float64 é o padrão para decimais e rune representa um carácter Unicode.
Strings básicas
s := "Olá Mundo" len(s) // comprimento em bytes s[0] // primeiro byte s[0:3] // substring "Olá" // Concatenar nome := "Olá, " + "Ana" // String bruta (multi-linha) texto := `linha 1 linha 2`
Strings são sequências imutáveis de bytes. len() devolve o tamanho em bytes e s[0:3] extrai uma parte. Crases criam strings brutas que aceitam várias linhas sem escapes.
Saída com fmt
import "fmt"
fmt.Println("Olá") // com newline
fmt.Print("sem ", "newline")
fmt.Printf("Nome: %s\n", "Ana")
// Verbos comuns
fmt.Printf("%d\n", 42) // inteiro
fmt.Printf("%.2f\n", 3.14159) // 3.14
fmt.Printf("%t\n", true) // bool
fmt.Printf("%v\n", objeto) // valor padrão
fmt.Printf("%+v\n", struct1) // com campos
fmt.Printf("%T\n", 3.14) // tipo
fmt.Sprintf("x=%d", 5) // retorna stringO pacote fmt imprime e formata. Println() adiciona quebra de linha e Printf() usa verbos: %d inteiro, %s string, %f float, %v valor padrão. Sprintf() retorna a string formatada.
Constantes e iota
const Pi = 3.14159
const (
StatusOK = 200
StatusNotFound = 404
)
// iota: contador automático
const (
Domingo = iota // 0
Segunda // 1
Terca // 2
Quarta // 3
)Constantes declaram-se com const e não podem mudar. Dentro de um bloco, iota gera números sequenciais automáticos (0, 1, 2...), ideal para enumerações.
Pacote strings
import "strings"
s := "Olá Mundo Go"
strings.Contains(s, "Mundo") // true
strings.HasPrefix(s, "Olá") // true
strings.HasSuffix(s, "Go") // true
strings.Split(s, " ") // [Olá Mundo Go]
strings.Join([]string{"a", "b"}, "-") // "a-b"
strings.Replace(s, "o", "0", -1)
strings.ToUpper(s)
strings.ToLower(s)
strings.Trim(" x ", " ") // "x"O pacote strings traz funções para manipular texto: Contains() procura, Split() divide e Join() junta. Replace() com -1 substitui todas as ocorrências.
Controlo de Fluxo
if / else
idade := 20
if idade >= 18 {
fmt.Println("adulto")
} else if idade > 12 {
fmt.Println("jovem")
} else {
fmt.Println("criança")
}O if em Go não usa parênteses na condição, mas as chavetas são obrigatórias. Podes encadear else if e else.
for como while e infinito
// while
x := 10
for x > 0 {
x--
}
// loop infinito
for {
if condicao {
break
}
}Go não tem while: usa-se for só com a condição. Um for sem nada cria um ciclo infinito, que termina com break ou return.
defer
f, err := os.Open("ficheiro.txt")
if err != nil {
return err
}
defer f.Close() // corre no fim
// vários defer: ordem LIFO
defer fmt.Println("1")
defer fmt.Println("2")
defer fmt.Println("3")
// saída: 3, 2, 1defer adia a execução de uma chamada para o final da função, ideal para limpeza (fechar ficheiros e ligações). Vários defer executam-se em ordem inversa (LIFO).
if com inicialização
if err := fazer(); err != nil {
log.Fatal(err)
}
// err não existe aqui
if n, ok := m["chave"]; ok {
fmt.Println(n)
}Podes declarar uma variável antes da condição, separada por ;. Ela fica limitada ao bloco do if. É o padrão idiomático para verificar erros e resultados.
break e continue
for i := 0; i < 10; i++ {
if i == 3 {
continue // salta o 3
}
if i == 7 {
break // pára no 7
}
fmt.Println(i)
}
// break com label (ciclos aninhados)
Loop:
for _, a := range x {
for _, b := range a {
if b == alvo {
break Loop
}
}
}continue salta para a próxima iteração e break termina o ciclo. Em ciclos aninhados, um label (como Loop:) permite ao break sair do ciclo exterior.
for clássico
for i := 0; i < 10; i++ {
fmt.Println(i)
}
// contador decrescente
for i := 10; i > 0; i-- {
fmt.Println(i)
}O for é o único ciclo em Go. Na forma clássica tem três partes: inicialização, condição e pós-instrução, separadas por ;. Não usa parênteses.
switch
dia := 2
switch dia {
case 1:
fmt.Println("Segunda")
case 2, 3:
fmt.Println("Terça ou Quarta")
case 4, 5:
fmt.Println("Quinta ou Sexta")
default:
fmt.Println("Fim de semana")
}O switch compara um valor com vários case. Em Go, cada caso termina automaticamente (sem break). Podes agrupar vários valores no mesmo case, separados por vírgula.
for range
// slice
for i, v := range slice {
fmt.Println(i, v)
}
// map
for k, v := range mapa {
fmt.Println(k, v)
}
// string (runes)
for i, r := range "Olá" {
fmt.Println(i, string(r))
}
// ignorar índice
for _, v := range slice { }range percorre slices, maps, strings e channels, devolvendo índice e valor. Usa _ para ignorar o índice. Em strings, itera por rune (carácter Unicode).
switch sem condição
hora := 14
switch {
case hora < 12:
fmt.Println("Bom dia")
case hora < 20:
fmt.Println("Boa tarde")
default:
fmt.Println("Boa noite")
}
// fallthrough (raro)
switch x {
case 1:
fmt.Println("um")
fallthrough
case 2:
fmt.Println("dois")
}Um switch sem expressão funciona como uma cadeia de if/else if, avaliando cada condição por ordem. fallthrough força a execução do caso seguinte (pouco usado).
Funções
Função simples
func somar(a, b int) int {
return a + b
}
// parâmetros do mesmo tipo
func media(a, b, c float64) float64 {
return (a + b + c) / 3
}
resultado := somar(3, 4) // 7Uma função declara-se com func, seguida do nome, parâmetros e tipo de retorno. Parâmetros do mesmo tipo podem partilhar o tipo (a, b int). O retorno vem no fim.
Funções como valor
var fn func(int) int
fn = func(x int) int {
return x * 2
}
fmt.Println(fn(5)) // 10
// em maps
operacoes := map[string]func(int, int) int{
"soma": func(a, b int) int { return a + b },
}Funções são valores de primeira classe em Go: podes guardá-las em variáveis, maps ou slices. O tipo func(int) int descreve uma função que recebe e devolve um int.
Recursão
func fatorial(n int) int {
if n <= 1 {
return 1
}
return n * fatorial(n-1)
}
func fibonacci(n int) int {
if n < 2 {
return n
}
return fibonacci(n-1) + fibonacci(n-2)
}
fmt.Println(fatorial(5)) // 120Uma função recursiva chama-se a si própria até atingir um caso base. fatorial() multiplica até chegar a 1. Recursão é elegante, mas para casos simples um ciclo pode ser mais eficiente.
Múltiplos retornos
func dividir(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("divisão por zero")
}
return a / b, nil
}
resultado, err := dividir(10, 2)
if err != nil {
log.Fatal(err)
}Go permite devolver vários valores. O padrão idiomático é retornar (resultado, error), onde error é nil quando tudo corre bem. O chamador verifica sempre o erro.
Funções anónimas
// executar imediatamente
func() {
fmt.Println("executada já")
}()
// com argumento
func(nome string) {
fmt.Println("Olá,", nome)
}("Ana")
// atribuir a variável
dobro := func(x int) int { return x * 2 }
fmt.Println(dobro(4)) // 8Uma função anónima não tem nome e pode ser definida e chamada no mesmo local. É muito usada em go func() {...}() para goroutines e como argumento de outras funções.
Retornos nomeados
func calcular(a, b int) (soma, produto int) {
soma = a + b
produto = a * b
return // retorna soma e produto
}
s, p := calcular(3, 4) // 7, 12Os retornos podem ter nome, servindo como variáveis locais. Um return sem valores devolve os valores nomeados atuais. Útil para documentar o significado de cada retorno.
Closures
func contador() func() int {
n := 0
return func() int {
n++
return n
}
}
inc := contador()
fmt.Println(inc()) // 1
fmt.Println(inc()) // 2
fmt.Println(inc()) // 3Uma closure é uma função que captura variáveis do ambiente onde foi criada. Aqui, a função anónima mantém o estado de n entre chamadas sucessivas.
Funções variádicas
func soma(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
soma(1, 2, 3) // 6
soma(1, 2, 3, 4, 5) // 15
// expandir um slice
vals := []int{1, 2, 3}
soma(vals...)Parâmetros com ... aceitam um número variável de argumentos, recebidos como um slice. Podes passar um slice existente expandindo-o com vals.... fmt.Println() é variádico.
Higher-order functions
// receber função como parâmetro
func aplicar(nums []int, fn func(int) int) []int {
res := make([]int, len(nums))
for i, v := range nums {
res[i] = fn(v)
}
return res
}
dobro := aplicar([]int{1, 2, 3}, func(x int) int {
return x * 2
}) // [2, 4, 6]Funções de ordem superior recebem ou devolvem outras funções. Passar uma função como parâmetro permite generalizar comportamento, como transformar cada elemento de um slice.
Structs e Interfaces
Struct
type Pessoa struct {
Nome string
Idade int
Ativo bool
}
// instanciar
p := Pessoa{
Nome: "Ana",
Idade: 30,
Ativo: true,
}
fmt.Println(p.Nome) // AnaUma struct agrupa campos de tipos diferentes num único tipo. Cria-se com type e instancia-se indicando os campos por nome. É a base para modelar dados em Go.
Construtores
type Utilizador struct {
Nome string
Email string
}
// convenção: New + nome do tipo
func NewUtilizador(nome, email string) *Utilizador {
return &Utilizador{
Nome: nome,
Email: email,
}
}
u := NewUtilizador("Ana", "ana@mail.com")Go não tem construtores nativos; a convenção é uma função NewX() que devolve um ponteiro para a struct já inicializada. Permite validar dados e definir valores por defeito.
Type assertion
var i any = "olá"
// afirmar o tipo
s := i.(string)
fmt.Println(s)
// forma segura (comma-ok)
s, ok := i.(string)
if ok {
fmt.Println("é string:", s)
}
n, ok := i.(int) // ok == falseUma type assertion (i.(string)) extrai o valor concreto de uma interface. A forma segura com ok evita um panic se o tipo não corresponder.
Formas de instanciar
type Ponto struct{ X, Y int }
// com nomes de campo (recomendado)
p1 := Ponto{X: 1, Y: 2}
// por posição (ordem dos campos)
p2 := Ponto{1, 2}
// zero value
var p3 Ponto // {0, 0}
// com new (retorna ponteiro)
p4 := new(Ponto)
p4.X = 5Podes instanciar uma struct com os nomes dos campos (mais claro) ou por posição. var p Ponto cria o zero value e new() devolve um ponteiro para uma struct zerada.
Interfaces
type Forma interface {
Area() float64
Perimetro() float64
}
type Quadrado struct{ Lado float64 }
func (q Quadrado) Area() float64 {
return q.Lado * q.Lado
}
func (q Quadrado) Perimetro() float64 {
return 4 * q.Lado
}
// Quadrado implementa Forma implicitamenteUma interface define um conjunto de métodos. Em Go, a implementação é implícita: qualquer type com esses métodos satisfaz a interface, sem declaração explícita. Isto favorece o desacoplamento.
Type switch
func descrever(i any) {
switch v := i.(type) {
case int:
fmt.Println("inteiro:", v)
case string:
fmt.Println("texto:", v)
case bool:
fmt.Println("bool:", v)
case []int:
fmt.Println("slice de int")
default:
fmt.Printf("outro: %T\n", v)
}
}Um type switch (i.(type)) testa o tipo concreto de uma interface em vários case. A variável v assume o tipo correto em cada ramo. %T mostra o tipo.
Métodos (receiver por valor)
type Circulo struct {
Raio float64
}
func (c Circulo) Area() float64 {
return math.Pi * c.Raio * c.Raio
}
c := Circulo{Raio: 5}
fmt.Println(c.Area())Um método é uma função com um receiver, declarado entre func e o nome. Com receiver por valor (c Circulo), o método trabalha sobre uma cópia e não altera o original.
Interface vazia (any)
// any é alias de interface{}
func imprimir(v any) {
fmt.Println(v)
}
imprimir(42)
imprimir("texto")
imprimir([]int{1, 2, 3})
var x any = "olá"A interface vazia any (alias de interface{}) aceita qualquer tipo, pois não exige métodos. É útil para funções genéricas, mas perde segurança de tipos — usa com moderação.
Métodos (receiver por ponteiro)
type Contador struct {
Valor int
}
func (c *Contador) Incrementar() {
c.Valor++ // altera o original
}
ct := Contador{}
ct.Incrementar()
ct.Incrementar()
fmt.Println(ct.Valor) // 2Com receiver por ponteiro (c *Contador), o método pode modificar o original e evita copiar structs grandes. Usa ponteiro sempre que precisares de alterar o estado.
Embedding (composição)
type Animal struct {
Nome string
}
func (a Animal) Descrever() string {
return "Sou " + a.Nome
}
type Cao struct {
Animal // campo embutido
Raca string
}
c := Cao{Animal: Animal{Nome: "Rex"}, Raca: "Labrador"}
fmt.Println(c.Nome) // promovido
fmt.Println(c.Descrever()) // método promovidoGo usa composição em vez de herança. Embutir um tipo (sem nome de campo) promove os seus campos e métodos para o tipo exterior. Cao ganha Nome e Descrever() de Animal.
Coleções
Arrays
var arr [5]int // [0,0,0,0,0]
arr[0] = 10
nums := [3]int{1, 2, 3}
// tamanho inferido
vals := [...]int{1, 2, 3, 4}
len(nums) // 3
// tamanho faz parte do tipo
var a [3]int
var b [4]int
// a = b // ERRO: tipos diferentesUm array tem tamanho fixo que faz parte do tipo ([3]int é diferente de [4]int). É passado por cópia. Na prática, usa-se quase sempre slice, que é dinâmico.
make e copy
// make: cria slice com len e cap
s := make([]int, 5) // len=5, cap=5
s = make([]int, 0, 10) // len=0, cap=10
// copy: copia elementos
src := []int{1, 2, 3}
dst := make([]int, len(src))
n := copy(dst, src) // n=3
// map pré-dimensionado
m := make(map[string]int, 100)make() cria slices, maps e channels com tamanho inicial, evitando realocações. copy(dst, src) copia elementos entre slices de forma segura, sem partilhar o array.
Ordenação (sort)
import "sort"
nums := []int{3, 1, 4, 1, 5}
sort.Ints(nums) // [1,1,3,4,5]
sort.Strings([]string{"b", "a", "c"})
// ordenação customizada
pessoas := []Pessoa{{"Ana", 30}, {"Rui", 25}}
sort.Slice(pessoas, func(i, j int) bool {
return pessoas[i].Idade < pessoas[j].Idade
})
sort.SearchInts(nums, 3) // pesquisa bináriaO pacote sort ordena slices: Ints() e Strings() para tipos básicos, sort.Slice() com uma função de comparação para tipos custom. SearchInts() faz pesquisa binária.
Slices
s := []int{1, 2, 3}
// slice vazio
var vazio []int // nil
outro := []int{} // vazio, não nil
len(s) // 3 (comprimento)
cap(s) // capacidade
s[0] // 1
s[2] // 3Um slice é uma vista dinâmica sobre um array, com comprimento e capacidade. É a coleção mais usada em Go. Declara-se com []tipo sem tamanho fixo.
Maps
m := map[string]int{
"maçã": 3,
"banana": 5,
}
// map vazio
vazio := make(map[string]int)
m["laranja"] = 2 // adicionar
m["maçã"] = 10 // atualizar
fmt.Println(m["banana"]) // 5
len(m)Um map guarda pares chave-valor. Cria-se com map[chave]valor{} ou make(). Aceder e atribuir usa a sintaxe m[chave]. As chaves devem ser comparáveis.
Pacote slices (Go 1.21+)
import "slices"
s := []int{3, 1, 2}
slices.Sort(s) // ordena
slices.Contains(s, 2) // true
slices.Index(s, 2) // posição
slices.Reverse(s) // inverte
slices.Min(s) // mínimo
slices.Max(s) // máximo
slices.Equal(a, b) // compara
slices.Clone(s) // cópia independenteO pacote slices (Go 1.21+) traz funções genéricas modernas: Contains(), Sort(), Min(), Max() e Clone(). Clone() cria uma cópia que não partilha o array.
append
s := []int{1, 2, 3}
s = append(s, 4) // [1,2,3,4]
s = append(s, 5, 6) // vários
s = append(s, []int{7, 8}...) // outro slice
// importante: reatribuir!
s = append(s, 9)append() adiciona elementos ao fim de um slice e devolve o novo slice — é preciso reatribuir (s = append(s, ...)). Se a capacidade chegar ao limite, o Go aloca um array maior automaticamente.
Maps: delete e comma-ok
m := map[string]int{"a": 1, "b": 2}
// comma-ok: a chave existe?
v, ok := m["a"]
if ok {
fmt.Println("existe:", v)
}
// valor zero se não existir
v = m["z"] // 0 (sem erro!)
delete(m, "a") // removerAceder a uma chave inexistente devolve o zero value sem erro. Por isso usa-se o padrão comma-ok (v, ok := m[chave]) para saber se a chave existe. delete() remove uma entrada.
Fatiar (slicing)
s := []int{0, 1, 2, 3, 4, 5}
s[1:4] // [1,2,3]
s[:3] // [0,1,2]
s[3:] // [3,4,5]
s[:] // vista completa
s[1:4:4] // low:high:max (limita cap)
// partilha o array subjacente!
sub := s[1:3]
sub[0] = 99 // altera s tambémA sintaxe s[low:high] cria um sub-slice. Atenção: o sub-slice partilha o array original, pelo que alterações afetam ambos. Usa copy() ou a forma de três índices para isolar.
Percorrer coleções
// slice
for i, v := range []int{10, 20, 30} {
fmt.Println(i, v)
}
// map (ordem aleatória!)
for k, v := range m {
fmt.Println(k, v)
}
// só chaves
for k := range m {
fmt.Println(k)
}range percorre slices (índice, valor) e maps (chave, valor). A ordem de iteração de um map é intencionalmente aleatória. Usa _ para ignorar o índice ou a chave.
Concorrência
Goroutines
func tarefa(n int) {
fmt.Println("tarefa", n)
}
func main() {
go tarefa(1) // goroutine
go func() {
fmt.Println("assíncrono")
}()
time.Sleep(time.Second) // espera simples
}Uma goroutine é uma função executada concorrentemente, lançada com go. São extremamente leves (poucos KB). O programa termina quando main() acaba, por isso é preciso sincronizar.
Fechar e percorrer channels
ch := make(chan int)
go func() {
for i := 0; i < 5; i++ {
ch <- i
}
close(ch) // fechar ao terminar
}()
// range termina quando o channel fecha
for v := range ch {
fmt.Println(v)
}
// comma-ok: channel fechado?
v, ok := <-chclose(ch) indica que não haverá mais envios. Um for range sobre o channel termina automaticamente quando fecha. O padrão v, ok := <-ch devolve ok == false se estiver fechado e vazio.
atomic e RWMutex
import "sync/atomic" var total int64 atomic.AddInt64(&total, 1) // incremento atómico v := atomic.LoadInt64(&total) // RWMutex: várias leituras, 1 escrita var rw sync.RWMutex rw.RLock() // leitura // ... ler dados ... rw.RUnlock() rw.Lock() // escrita // ... escrever ... rw.Unlock()
sync/atomic faz operações atómicas sem locks, ideal para contadores simples. sync.RWMutex permite várias leituras simultâneas (RLock()) mas apenas uma escrita (Lock()).
Channels
ch := make(chan string)
go func() {
ch <- "olá" // envia (bloqueia)
}()
msg := <-ch // recebe (bloqueia)
fmt.Println(msg)Um channel comunica entre goroutines de forma segura. ch <- valor envia e <-ch recebe. Um channel sem buffer bloqueia até haver um recetor e um emissor prontos.
select
select {
case msg := <-ch1:
fmt.Println("ch1:", msg)
case msg := <-ch2:
fmt.Println("ch2:", msg)
case <-time.After(time.Second):
fmt.Println("timeout")
default:
fmt.Println("nenhum pronto")
}select espera por várias operações de channel, executando o primeiro caso pronto. Se vários estiverem prontos, escolhe aleatoriamente. default evita o bloqueio e time.After() cria timeouts.
Context
ctx, cancel := context.WithTimeout(
context.Background(),
5*time.Second,
)
defer cancel()
select {
case res := <-fazer(ctx):
fmt.Println(res)
case <-ctx.Done():
fmt.Println("cancelado:", ctx.Err())
}O pacote context controla cancelamento e timeouts entre goroutines. WithTimeout() cria um contexto que expira; ctx.Done() é um channel que fecha ao cancelar. Passa ctx como primeiro parâmetro.
Channel com buffer
ch := make(chan int, 3) // buffer de 3 ch <- 1 // não bloqueia ch <- 2 ch <- 3 // ch <- 4 // BLOQUEIA (cheio) fmt.Println(<-ch) // 1 (FIFO) fmt.Println(len(ch), cap(ch)) // 2 3
Um channel com buffer (make(chan int, 3)) aceita envios sem bloquear enquanto houver espaço. Só bloqueia quando está cheio (envio) ou vazio (receção).
WaitGroup
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(n int) {
defer wg.Done()
fmt.Println(n)
}(i)
}
wg.Wait() // bloqueia até todas terminarem
fmt.Println("feito")sync.WaitGroup espera que um conjunto de goroutines termine. Add(1) regista cada uma, Done() (em defer) marca a conclusão e Wait() bloqueia até todas acabarem.
Channels direcionais
// só envia
func produtor(ch chan<- int) {
ch <- 42
}
// só recebe
func consumidor(ch <-chan int) {
fmt.Println(<-ch)
}
ch := make(chan int)
go produtor(ch)
consumidor(ch)Podes restringir a direção de um channel no tipo: chan<- int só envia, <-chan int só recebe. Isto torna as APIs mais claras e seguras, indicando quem produz e quem consome.
Mutex
var (
mu sync.Mutex
contador int
)
func incrementar() {
mu.Lock()
defer mu.Unlock()
contador++
}
// 1000 goroutines seguras
for i := 0; i < 1000; i++ {
go incrementar()
}Um sync.Mutex protege dados partilhados: Lock() bloqueia o acesso e Unlock() liberta-o. Usa defer mu.Unlock() para garantir a libertação mesmo em caso de erro.
Tratamento de Erros
Padrão error
f, err := os.Open("dados.txt")
if err != nil {
return fmt.Errorf("abrir ficheiro: %w", err)
}
defer f.Close()
// usar fEm Go, erros são valores. Funções que podem falhar devolvem um error como último retorno. O padrão idiomático é verificar if err != nil imediatamente e propagar o erro.
errors.Is
import (
"errors"
"os"
)
_, err := os.Open("inexistente.txt")
if errors.Is(err, os.ErrNotExist) {
fmt.Println("ficheiro não existe")
}
// compara ao longo da cadeia de wrappingerrors.Is(err, alvo) verifica se um erro (ou qualquer erro envolvido na cadeia) corresponde a um valor específico, como os.ErrNotExist. Substitui a comparação direta, que não funciona com wrapping.
Criar erros
import (
"errors"
"fmt"
)
// erro simples
err := errors.New("algo falhou")
// erro formatado
err = fmt.Errorf("valor %d é inválido", n)
return errerrors.New() cria um erro com uma mensagem fixa. fmt.Errorf() cria erros formatados, como Sprintf. Ambos devolvem um valor que implementa a interface error.
errors.As
var nf *NotFoundError
if errors.As(err, &nf) {
// nf agora tem o tipo concreto
fmt.Println("faltou:", nf.Nome)
}
// extrai o primeiro erro do tipo indicadoerrors.As(err, &alvo) procura na cadeia o primeiro erro de um tipo concreto e copia-o para alvo. Permite aceder a campos específicos de erros customizados mesmo após wrapping.
Wrapping com %w
func lerConfig() error {
_, err := os.Open("config.yaml")
if err != nil {
return fmt.Errorf("ler config: %w", err)
}
return nil
}
// %w envolve o erro original,
// preservando a cadeia de causasO verbo %w em fmt.Errorf() envolve o erro original, criando uma cadeia. Isto permite inspecionar a causa mais tarde com errors.Is() e errors.As(), sem perder contexto.
errors.Join (Go 1.20+)
import "errors"
err1 := validarNome(nome)
err2 := validarEmail(email)
// combinar vários erros
err := errors.Join(err1, err2)
if err != nil {
return err // mostra ambos
}
errors.Is(err, err1) // trueerrors.Join() (Go 1.20+) combina vários erros num só, útil para validar múltiplos campos. Os erros originais continuam detetáveis com errors.Is() e errors.As().
Erro customizado
type NotFoundError struct {
Nome string
}
func (e *NotFoundError) Error() string {
return e.Nome + " não encontrado"
}
// usar
return &NotFoundError{Nome: "utilizador"}Qualquer tipo com um método Error() string implementa a interface error. Erros customizados podem transportar dados adicionais (como Nome), úteis para o chamador.
panic e recover
func seguro() {
defer func() {
if r := recover(); r != nil {
fmt.Println("recuperado:", r)
}
}()
panic("erro fatal")
}
// panic interrompe; recover só em deferpanic() interrompe o fluxo e propaga-se pela pilha; recover(), chamado dentro de um defer, captura-o e retoma o controlo. É último recurso — em Go, prefere devolver error.
Pacotes e Módulos
go.mod
module github.com/utilizador/projeto
go 1.22
require (
github.com/gorilla/mux v1.8.1
)O ficheiro go.mod define o módulo: o caminho (nome) e a versão do Go. Cria-se com go mod init. As dependências ficam em require e as versões exatas em go.sum.
go get
# adicionar dependência go get github.com/gorilla/mux # versão específica go get github.com/gorilla/mux@v1.8.1 # atualizar go get -u ./... # remover não usadas go mod tidy
go get descarrega e adiciona dependências ao go.mod. Podes fixar uma versão com @v1.8.1 e atualizar tudo com -u. go mod tidy limpa as que já não são usadas.
import
import (
"fmt"
"os"
"net/http"
alias "outro/pacote" // renomear
_ "driver/mysql" // só side-effects
. "math" // dot import (evitar)
)
fmt.Println("olá")import traz pacotes para o ficheiro. Podes dar um alias para evitar conflitos, usar _ para executar apenas a inicialização (drivers) ou . para importar sem prefixo (desaconselhado).
go mod
# criar módulo go mod init github.com/user/proj # limpar dependências go mod tidy # descarregar todas go mod download # verificar integridade go mod verify # ver o grafo de dependências go mod graph
Os comandos go mod gerem o módulo: init cria o go.mod, tidy sincroniza as dependências com o código, download guarda-as na cache e verify confirma as somas.
Exportação (maiúscula)
package calculo
// Maiúscula = exportado (público)
func Somar(a, b int) int {
return a + b
}
type Servidor struct {
Host string // campo público
porta int // campo privado
}
// minúscula = privado ao pacote
func interno() { }Em Go, a visibilidade é por capitalização: nomes com maiúscula (Somar, Servidor) são exportados e acessíveis fora do pacote. Nomes com minúscula são privados ao pacote.
Organização de projeto
meuprojeto/
├── go.mod
├── go.sum
├── main.go // package main
├── cmd/
│ └── server/
│ └── main.go // outro binário
├── internal/ // privado ao módulo
│ └── handler/
└── pkg/ // reutilizável
└── util/Um projeto Go organiza-se por pacotes em pastas. cmd/ guarda os binários, internal/ código privado ao módulo (o compilador impede import externo) e pkg/ código reutilizável.
init()
package config
var baseURL string
func init() {
// executado antes do main()
baseURL = os.Getenv("API_URL")
if baseURL == "" {
baseURL = "http://localhost"
}
}A função init() corre automaticamente antes do main(), uma vez por pacote. Serve para inicializar estado, registar drivers ou validar configuração. Pode haver várias por ficheiro.
package main
package main
import "fmt"
// ponto de entrada obrigatório
func main() {
fmt.Println("Olá, mundo!")
}Um programa executável precisa de package main e de uma função main() sem parâmetros nem retorno. É o ponto de entrada. Pacotes de biblioteca usam outros nomes.
Dicas e CLI
CLI: comandos essenciais
go run main.go # compila e executa go build # gera binário go install # compila e instala go test ./... # corre testes go fmt ./... # formata código go mod tidy # limpa dependências go clean # limpa caches
O Go tem uma ferramenta única, go. run compila e executa, build gera o binário, test corre os testes e fmt formata o código no estilo padrão. ./... abrange todos os pacotes.
Interfaces úteis
// fmt.Stringer
type Ponto struct{ X, Y int }
func (p Ponto) String() string {
return fmt.Sprintf("(%d,%d)", p.X, p.Y)
}
// io.Reader / io.Writer
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}A biblioteca padrão define interfaces pequenas e ubíquas. fmt.Stringer (String()) personaliza a impressão. io.Reader e io.Writer abstraem qualquer fonte ou destino de dados.
Boas práticas
// 1. Tratar sempre os erros
if err != nil {
return err
}
// 2. Nomes curtos e claros
i, v := range s // não "index, value"
// 3. Composição em vez de herança
// 4. Receptor curto
func (s *Servidor) Start() { }
// 5. Context como 1.º parâmetro
func Get(ctx context.Context, id string) { }Go valoriza simplicidade: trata sempre os erros, usa nomes curtos, prefere composição a herança e passa context.Context como primeiro parâmetro. Formata sempre com go fmt.
Testes (testing)
// soma_test.go
package calculo
import "testing"
func TestSomar(t *testing.T) {
got := Somar(2, 3)
want := 5
if got != want {
t.Errorf("Somar(2,3) = %d; quero %d", got, want)
}
}Testes ficam em ficheiros _test.go com funções TestX(t *testing.T). Corre-os com go test. t.Errorf() regista uma falha sem parar; t.Fatalf() pára o teste.
io.Reader na prática
import (
"io"
"os"
"strings"
)
r := strings.NewReader("olá mundo")
// copiar de um Reader para um Writer
io.Copy(os.Stdout, r)
// ler tudo
data, _ := io.ReadAll(r)
// ficheiro é um Reader
f, _ := os.Open("f.txt")
io.Copy(os.Stdout, f)io.Reader é a abstração central de entrada. io.Copy() transfere dados de um Reader para um Writer de forma eficiente. Ficheiros, strings e respostas HTTP são todos Reader.
Table-driven tests
func TestSomar(t *testing.T) {
testes := []struct {
a, b, want int
}{
{1, 2, 3},
{0, 0, 0},
{-1, 1, 0},
}
for _, tt := range testes {
if got := Somar(tt.a, tt.b); got != tt.want {
t.Errorf("Somar(%d,%d) = %d; quero %d",
tt.a, tt.b, got, tt.want)
}
}
}Testes orientados por tabela definem vários casos numa slice de structs e iteram sobre eles com range. É o padrão idiomático em Go: compacto, fácil de estender e com mensagens de erro claras.
JSON
type Pessoa struct {
Nome string `json:"nome"`
Idade int `json:"idade"`
}
// struct -> JSON
data, _ := json.Marshal(Pessoa{"Ana", 30})
// JSON -> struct
var p Pessoa
json.Unmarshal(data, &p)
// com formatação
json.MarshalIndent(p, "", " ")O pacote encoding/json serializa structs. Marshal() converte para JSON e Unmarshal() faz o inverso. As tags json:"nome" definem os nomes dos campos no JSON.
go vet e formatação
# análise estática go vet ./... # formatar go fmt ./... # formatar + organizar imports goimports -w . # linter completo (externo) golangci-lint run
go vet deteta erros suspeitos que compilam mas estão errados. go fmt formata o código no estilo oficial. goimports também organiza os imports automaticamente.
Generics (Go 1.18+)
// type parameter
func Max[T constraints.Ordered](a, b T) T {
if a > b {
return a
}
return b
}
Max(3, 7) // 7
Max("a", "b") // "b"
// slice genérico
func Map[T, U any](s []T, f func(T) U) []U {
res := make([]U, len(s))
for i, v := range s {
res[i] = f(v)
}
return res
}Generics permitem funções e tipos parametrizados por tipo, com [T ...]. A restrição constraints.Ordered limita T a tipos ordenáveis. Reduz código duplicado mantendo segurança de tipos.