Múltiplos retornos: o padrão valor + erro
Em Go, funções podem retornar mais de um valor. O uso mais idiomático é retornar (valor, error), onde error indica se a operação falhou. A regra prática é: se err != nil, o valor retornado deve ser considerado inválido (ou um valor “zero” seguro).
Exemplo: parse com validação
package main
import (
"errors"
"fmt"
"strconv"
)
func parsePositiveInt(s string) (int, error) {
n, err := strconv.Atoi(s)
if err != nil {
return 0, err
}
if n <= 0 {
return 0, errors.New("valor deve ser positivo")
}
return n, nil
}
func main() {
n, err := parsePositiveInt("10")
if err != nil {
fmt.Println("erro:", err)
return
}
fmt.Println("ok:", n)
}
Passo a passo (como pensar)
- Faça a operação que pode falhar (ex.: conversão, I/O, validação).
- Se falhar, retorne o valor zero do tipo + o erro.
- Se der certo, retorne o valor +
nil. - No chamador, trate o erro imediatamente (early return).
Retornos nomeados (quando fazem sentido)
Você pode nomear retornos para melhorar legibilidade em funções com múltiplas saídas ou quando o retorno “carrega significado”. Use com moderação: retornos nomeados ajudam quando o nome adiciona clareza, mas podem confundir se usados sem necessidade.
func minMax(nums []int) (min int, max int, err error) {
if len(nums) == 0 {
return 0, 0, fmt.Errorf("slice vazio")
}
min, max = nums[0], nums[0]
for _, v := range nums[1:] {
if v < min {
min = v
}
if v > max {
max = v
}
}
return min, max, nil
}
Note que return “nu” (sem valores) retorna os valores atuais das variáveis nomeadas. Prefira return min, max, nil se isso deixar mais explícito.
Funções variádicas: recebendo N argumentos
Uma função variádica recebe zero ou mais argumentos de um tipo, declarados com .... Internamente, o parâmetro variádico é tratado como um slice.
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
Chamando com valores soltos
total := sum(1, 2, 3)
Passando um slice para uma variádica
Para “expandir” um slice em argumentos variádicos, use ... na chamada.
- Ouça o áudio com a tela desligada
- Ganhe Certificado após a conclusão
- + de 5000 cursos para você explorar!
Baixar o aplicativo
nums := []int{1, 2, 3}
total := sum(nums...)
Variádica + parâmetros fixos
O parâmetro variádico deve ser o último.
func join(sep string, parts ...string) string {
if len(parts) == 0 {
return ""
}
out := parts[0]
for _, p := range parts[1:] {
out += sep + p
}
return out
}
Cuidados comuns
partsé um slice: pode ser iterado, terlen, etc.- Evite modificar o slice variádico se você não controla quem chamou (pode surpreender se o chamador passou um slice com
...).
Closures: funções que “capturam” variáveis
Closure é uma função que referencia variáveis do escopo onde foi criada. Em Go, isso permite criar funções geradoras, filtros e callbacks que carregam “estado” sem precisar de structs.
Exemplo 1: filtro (predicado) com parâmetro capturado
Vamos criar uma função que gera um filtro “maior que X”.
func greaterThan(min int) func(int) bool {
return func(v int) bool {
return v > min
}
}
func filterInts(nums []int, keep func(int) bool) []int {
out := make([]int, 0, len(nums))
for _, n := range nums {
if keep(n) {
out = append(out, n)
}
}
return out
}
Uso:
nums := []int{1, 5, 7, 2, 9}
keep := greaterThan(5)
filtered := filterInts(nums, keep) // [7 9]
Passo a passo (o que acontece)
greaterThan(5)cria uma função anônima.- A função anônima “captura”
min(que vale 5 naquele momento). - Ao chamar
keep(7), ela compara com omincapturado.
Exemplo 2: função geradora com estado (contador)
func counter(start int) func() int {
n := start
return func() int {
n++
return n
}
}
Uso:
next := counter(10)
fmt.Println(next()) // 11
fmt.Println(next()) // 12
A variável n permanece viva enquanto a closure existir, funcionando como “estado privado”.
Escopo e armadilha comum: capturar variável de loop
Um erro frequente é capturar a variável do for e esperar que cada closure “lembre” um valor diferente. Em Go, a variável do loop é reutilizada; closures podem acabar vendo o mesmo valor final.
// Exemplo problemático (não faça assim)
func bad() []func() int {
fs := []func() int{}
for i := 0; i < 3; i++ {
fs = append(fs, func() int { return i })
}
return fs
}
Correção: crie uma variável local por iteração.
func good() []func() int {
fs := []func() int{}
for i := 0; i < 3; i++ {
i := i // sombra intencional: novo i por iteração
fs = append(fs, func() int { return i })
}
return fs
}
defer: adiar execução e garantir limpeza
defer agenda uma chamada de função para executar quando a função atual retornar (seja por retorno normal, por erro, ou por pânico não recuperado). É muito usado para liberar recursos (fechar arquivos, liberar locks, etc.).
Ordem de execução: LIFO (pilha)
Vários defer executam em ordem inversa à que foram declarados.
func demoDeferOrder() {
defer fmt.Println("3")
defer fmt.Println("2")
defer fmt.Println("1")
fmt.Println("0")
}
// Saída:
// 0
// 1
// 2
// 3
Quando os argumentos são avaliados
Os argumentos da chamada adiada são avaliados no momento do defer, não no momento da execução.
func demoDeferArgs() {
x := 10
defer fmt.Println("x:", x) // captura 10 agora
x = 20
}
// imprime: x: 10
Padrão comum: fechar recursos
Ao abrir um recurso, faça defer Close() imediatamente após verificar o erro. Isso reduz vazamentos e simplifica retornos antecipados.
func readAll(r io.ReadCloser) ([]byte, error) {
defer r.Close()
return io.ReadAll(r)
}
Em casos reais, você normalmente obtém o io.ReadCloser de uma função que pode falhar; o padrão é:
rc, err := openSomething()
if err != nil {
return nil, err
}
defer rc.Close()
defer com retorno nomeado: ajustando o erro
Quando você usa retorno nomeado, um defer pode ler e até modificar o valor que será retornado. Isso é útil para enriquecer erros com contexto.
func doWork() (err error) {
defer func() {
if err != nil {
err = fmt.Errorf("doWork falhou: %w", err)
}
}()
err = step1()
if err != nil {
return err
}
err = step2()
if err != nil {
return err
}
return nil
}
Recuperando pânico com defer + recover (quando aplicável)
panic interrompe o fluxo normal. Em bibliotecas e serviços, você pode usar recover para evitar que um pânico derrube o programa inteiro, convertendo-o em erro. recover só funciona quando chamado dentro de uma função adiada (defer).
Wrapper seguro: converter pânico em erro
func safeRun(fn func()) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("panic recuperado: %v", r)
}
}()
fn()
return nil
}
Uso:
err := safeRun(func() {
panic("algo inesperado")
})
if err != nil {
fmt.Println("erro:", err)
}
Boas práticas ao usar recover
- Use para proteger fronteiras (handlers, goroutines, jobs), não como controle de fluxo normal.
- Registre contexto suficiente (o que estava sendo feito) ao transformar em erro.
- Se fizer sentido, repropague (panic novamente) após logar, dependendo do tipo de aplicação.
Exercícios
1) Múltiplos retornos (valor + erro)
- Implemente
func divide(a, b float64) (float64, error)que retorna erro quandob == 0. - Implemente
func parsePort(s string) (port int, err error)que valida se o número está entre 1 e 65535 (use retorno nomeado apenas se ajudar a clarear).
2) Variádicas e slices
- Implemente
func avg(nums ...float64) (float64, error)que retorna erro se nenhum número for passado. - Dado um slice
[]float64, chameavgexpandindo com.... - Implemente
func concat(sep string, parts ...string) stringsem usarstrings.Join.
3) Closures (filtros e geradores)
- Crie
func between(min, max int) func(int) boole use com umfilterIntspara filtrar números dentro do intervalo. - Crie
func multiplier(factor int) func(int) inte aplique em um slice (faça uma funçãomapInts). - Crie um gerador
func fibonacci() func() intque retorna o próximo número da sequência a cada chamada.
4) defer (ordem e limpeza)
- Escreva uma função que imprime
start, agenda trêsdefercom prints diferentes e imprimeend. Verifique a ordem. - Implemente
func withLock(mu *sync.Mutex, fn func())que fazmu.Lock()e garantemu.Unlock()comdefer.
5) defer + recover
- Implemente
func safeValue(fn func() int) (v int, err error)que recupera pânico e retorna um erro. - Crie um teste manual chamando
safeValuecom uma função que dápanice outra que retorna normalmente.