Programação em Go do Zero com 25 Exemplos

Go do Zero com 25 Exemplos

Go (Golang) é uma linguagem criada no Google. É simples, compila rapidamente e tem concorrência embutida, por isso é popular em servidores, ferramentas e sistemas de nuvem. Este artigo tem 25 exemplos de código, cada um executado com Go 1.27, e a saída real de cada um. Os exemplos vão do básico à concorrência, passando por handlers web e generics.

O que você precisa

Instale o Go em go.dev/dl e verifique com `go version`. Crie um arquivo `.go` e execute com `go run nomedoarquivo.go`.

Ilustração

Diagrama mostrando o fluxo de trabalho e a ideia de goroutines com channels (imagem esquemática, não uma captura de tela real).
Diagrama mostrando o fluxo de trabalho e a ideia de goroutines com channels (imagem esquemática, não uma captura de tela real).

Exemplo 1: Olá, mundo

O programa Go mais simples. Todo programa começa com o pacote main e a função main.

package main

import "fmt"

func main() {
	fmt.Println("Olá, Go")
}

Saída real

Olá, Go

Exemplo 2: Variáveis e constantes

`:=` declara e atribui valor em um único passo, `var` declara de forma explícita, e `const` é usado para valores que nunca mudam.

package main

import "fmt"

const schoolName = "Escola Exemplo"

func main() {
	name := "Budi"
	var age int = 20
	fmt.Println(schoolName, name, age)
}

Saída real

Escola Exemplo Budi 20

Exemplo 3: Valores zero

Uma variável declarada sem valor recebe o valor zero do seu tipo, como 0, string vazia ou false.

package main

import "fmt"

func main() {
	var n int
	var s string
	var b bool
	var f float64
	fmt.Printf("int=%d string=%q bool=%t float=%.1f\n", n, s, b, f)
}

Saída real

int=0 string="" bool=false float=0.0

Exemplo 4: if e else

Tomada de decisão com if e else if. Go não exige parênteses em volta da condição.

package main

import "fmt"

func main() {
	score := 72
	if score >= 80 {
		fmt.Println("A")
	} else if score >= 70 {
		fmt.Println("B")
	} else {
		fmt.Println("C")
	}
}

Saída real

B

Exemplo 5: Laço for

Go tem apenas uma palavra-chave de repetição, o for, que pode contar ou funcionar como um while.

package main

import "fmt"

func main() {
	for i := 1; i <= 3; i++ {
		fmt.Println("contagem", i)
	}
	n := 0
	for n < 2 {
		n++
	}
	fmt.Println("n =", n)
}

Saída real

contagem 1
contagem 2
contagem 3
n = 2

Exemplo 6: switch

O switch compara vários casos e não passa para o próximo caso a menos que você peça.

package main

import "fmt"

func main() {
	day := "Sábado"
	switch day {
	case "Sábado", "Domingo":
		fmt.Println("dia de folga")
	default:
		fmt.Println("dia útil")
	}
}

Saída real

dia de folga

Exemplo 7: Arrays e slices

Arrays têm tamanho fixo. Slices crescem com append e podem ser fatiados com s[a:b].

package main

import "fmt"

func main() {
	arr := [3]int{1, 2, 3}
	s := []int{10, 20}
	s = append(s, 30, 40)
	fmt.Println("array:", arr, "len:", len(arr))
	fmt.Println("slice:", s, "len:", len(s), "cap:", cap(s) >= 4)
	fmt.Println("fatia:", s[1:3])
}

Saída real

array: [1 2 3] len: 3
slice: [10 20 30 40] len: 4 cap: true
fatia: [20 30]

Exemplo 8: Map

Um map guarda pares chave-valor. A forma `v, ok :=` verifica se uma chave existe.

package main

import "fmt"

func main() {
	scores := map[string]int{"Budi": 85, "Siti": 72}
	scores["Andi"] = 45
	if v, ok := scores["Budi"]; ok {
		fmt.Println("nota:", v)
	}
	delete(scores, "Siti")
	fmt.Println("quantidade:", len(scores))
}

Saída real

nota: 85
quantidade: 2

Exemplo 9: Função com vários retornos

Uma função pode devolver vários valores. Erros geralmente são devolvidos como último valor.

package main

import (
	"errors"
	"fmt"
)

func divide(a, b int) (int, error) {
	if b == 0 {
		return 0, errors.New("não é possível dividir por zero")
	}
	return a / b, nil
}

func main() {
	q, err := divide(10, 2)
	fmt.Println("resultado:", q, "erro:", err)
	_, err = divide(1, 0)
	fmt.Println("erro:", err)
}

Saída real

resultado: 5 erro: <nil>
erro: não é possível dividir por zero

Exemplo 10: Closure

Uma função que lembra variáveis do escopo em que foi criada. Útil para contadores e estado privado.

package main

import "fmt"

func counter() func() int {
	count := 0
	return func() int {
		count++
		return count
	}
}

func main() {
	next := counter()
	fmt.Println(next(), next(), next())
}

Saída real

1 2 3

Exemplo 11: Struct e método

Uma struct agrupa dados, e um método dá comportamento a esse tipo.

package main

import "fmt"

type Student struct {
	Name  string
	Score int
}

func (s Student) Grade() string {
	if s.Score >= 80 {
		return "A"
	}
	return "B"
}

func main() {
	s := Student{Name: "Budi", Score: 85}
	fmt.Println(s.Name, s.Grade())
}

Saída real

Budi A

Exemplo 12: Interface

Uma interface lista os métodos que um tipo precisa ter. Qualquer tipo que tenha esses métodos satisfaz a interface automaticamente.

package main

import "fmt"

type Shape interface {
	Area() float64
}

type Rect struct{ W, H float64 }

func (r Rect) Area() float64 { return r.W * r.H }

func main() {
	var s Shape = Rect{W: 3, H: 4}
	fmt.Printf("área = %.1f\n", s.Area())
}

Saída real

área = 12.0

Exemplo 13: Ponteiros

&x fornece o endereço de uma variável, e *p lê ou altera o valor nesse endereço, então uma função pode alterar os dados de quem a chamou.

package main

import "fmt"

func increment(n *int) {
	*n++
}

func main() {
	x := 5
	increment(&x)
	fmt.Println("x =", x)
}

Saída real

x = 6

Exemplo 14: Tratando erros

Crie um erro com fmt.Errorf e %w, depois verifique com errors.Is se ele corresponde a um erro conhecido.

package main

import (
	"errors"
	"fmt"
)

var ErrNotFound = errors.New("dado não encontrado")

func find(id int) error {
	if id != 1 {
		return fmt.Errorf("código %d: %w", id, ErrNotFound)
	}
	return nil
}

func main() {
	err := find(2)
	fmt.Println(err)
	fmt.Println("corresponde a ErrNotFound:", errors.Is(err, ErrNotFound))
}

Saída real

código 2: dado não encontrado
corresponde a ErrNotFound: true

Exemplo 15: defer

defer adia uma chamada até a função que a contém terminar. É ideal para fechar arquivos e liberar recursos.

package main

import "fmt"

func main() {
	defer fmt.Println("executado por último")
	fmt.Println("início")
	fmt.Println("processando")
}

Saída real

início
processando
executado por último

Exemplo 16: panic e recover

panic interrompe a execução normal, e recover só pode capturá-lo dentro de uma função que usa defer, para que o programa não trave.

package main

import "fmt"

func safeDivide(a, b int) (result int, err error) {
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("recuperado de panic: %v", r)
		}
	}()
	return a / b, nil
}

func main() {
	fmt.Println(safeDivide(10, 2))
	fmt.Println(safeDivide(1, 0))
}

Saída real

5 <nil>
0 recuperado de panic: runtime error: integer divide by zero

Exemplo 17: Goroutine

A palavra-chave go inicia trabalho que roda de forma concorrente. sync.WaitGroup espera até que todo o trabalho termine.

package main

import (
	"fmt"
	"sync"
)

func main() {
	var wg sync.WaitGroup
	for i := 1; i <= 3; i++ {
		wg.Add(1)
		go func(n int) {
			defer wg.Done()
			_ = n * n
		}(i)
	}
	wg.Wait()
	fmt.Println("todas as tarefas concluídas")
}

Saída real

todas as tarefas concluídas

Exemplo 18: Channel

Channels enviam valores entre goroutines. Este exemplo envia valores de uma goroutine para main e depois fecha o channel.

package main

import "fmt"

func main() {
	ch := make(chan int)
	go func() {
		for i := 1; i <= 3; i++ {
			ch <- i * 10
		}
		close(ch)
	}()
	for v := range ch {
		fmt.Println("recebeu", v)
	}
}

Saída real

recebeu 10
recebeu 20
recebeu 30

Exemplo 19: select e timeout

select espera vários channels ao mesmo tempo. time.After define um tempo máximo de espera.

package main

import (
	"fmt"
	"time"
)

func main() {
	result := make(chan string)
	go func() {
		time.Sleep(200 * time.Millisecond)
		result <- "concluído"
	}()
	select {
	case msg := <-result:
		fmt.Println(msg)
	case <-time.After(50 * time.Millisecond):
		fmt.Println("tempo esgotado")
	}
}

Saída real

tempo esgotado

Exemplo 20: Mutex para dados compartilhados

sync.Mutex permite que muitas goroutines alterem uma mesma variável com segurança. O total está sempre correto.

package main

import (
	"fmt"
	"sync"
)

func main() {
	var mu sync.Mutex
	var wg sync.WaitGroup
	total := 0
	for i := 0; i < 100; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			mu.Lock()
			total++
			mu.Unlock()
		}()
	}
	wg.Wait()
	fmt.Println("total =", total)
}

Saída real

total = 100
Diagrama mostrando o fluxo de trabalho e a ideia de goroutines com channels (imagem esquemática, não uma captura de tela real).
Diagrama mostrando o fluxo de trabalho e a ideia de goroutines com channels (imagem esquemática, não uma captura de tela real).

Exemplo 21: Pacotes strings e rune

strings oferece funções de texto. len conta bytes, então conte caracteres não ASCII convertendo para []rune.

package main

import (
	"fmt"
	"strings"
)

func main() {
	s := "Go é fácil de aprender"
	fmt.Println(strings.ToUpper("go"), strings.Contains(s, "fácil"))
	fmt.Println(strings.Split("a,b,c", ","), len("café"), len([]rune("café")))
	fmt.Println([]rune("café")[3] == 'é')
}

Saída real

GO true
[a b c] 5 4
true

Exemplo 22: Lendo e gravando arquivos

os.WriteFile e os.ReadFile gravam e leem o arquivo inteiro em uma única chamada.

package main

import (
	"fmt"
	"os"
)

func main() {
	if err := os.WriteFile("nota.txt", []byte("anotação do Go\n"), 0644); err != nil {
		fmt.Println("falha ao gravar:", err)
		return
	}
	data, err := os.ReadFile("nota.txt")
	if err != nil {
		fmt.Println("falha ao ler:", err)
		return
	}
	fmt.Print("lido: ", string(data))
}

Saída real

lido: anotação do Go

Exemplo 23: JSON

encoding/json converte uma struct em JSON e vice-versa. Tags como `json:"..."` definem o nome dos campos.

package main

import (
	"encoding/json"
	"fmt"
)

type Student struct {
	Name  string `json:"name"`
	Score int    `json:"score"`
}

func main() {
	b, _ := json.Marshal(Student{Name: "Budi", Score: 85})
	fmt.Println(string(b))

	var s Student
	_ = json.Unmarshal([]byte(`{"name":"Siti","score":72}`), &s)
	fmt.Println(s.Name, s.Score)
}

Saída real

{"name":"Budi","score":85}
Siti 72

Exemplo 24: Testando um handler HTTP

httptest testa uma função handler sem executar um servidor de verdade.

package main

import (
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
)

func hello(w http.ResponseWriter, r *http.Request) {
	fmt.Fprint(w, "Olá do Go HTTP")
}

func main() {
	req := httptest.NewRequest(http.MethodGet, "/", nil)
	rec := httptest.NewRecorder()
	hello(rec, req)
	body, _ := io.ReadAll(rec.Result().Body)
	fmt.Println(rec.Code, string(body))
}

Saída real

200 Olá do Go HTTP

Exemplo 25: Generics

Funções que trabalham com vários tipos usando um parâmetro de tipo T, restrito por uma interface.

package main

import "fmt"

type Number interface {
	~int | ~float64
}

func Sum[T Number](values []T) T {
	var total T
	for _, v := range values {
		total += v
	}
	return total
}

func main() {
	fmt.Println(Sum([]int{1, 2, 3}))
	fmt.Println(Sum([]float64{1.5, 2.5}))
}

Saída real

6
4

Próximos passos

Tente escrever um servidor web que recebe JSON e o salva em um arquivo. Depois, estude o pacote net/http e o context, para timeouts e cancelamento.

Resumo

Go tem uma sintaxe simples, verifica tipos na compilação, trata erros como valores e oferece concorrência com goroutines e channels, o que o torna adequado para servidores e cargas de trabalho com muitas tarefas simultâneas.