Funções em Go
De modo geral, e como você já deve ter percebido, o GoLang assim como toda e qualquer linguagem de programação, conta com uma estrutura de blocos popularmente conhecidas como funções.
Elas são consideradas "subprogramas" de um programa principal, por vezes, chamadas de código externo/interno de uma aplicação.
As funções seguem um esquema bastante similar às estruturas condicionais (if/else), e também com as estruturas de iterações (for, while...), isso porque, ela também trabalha com blocos de código.
Nesta lição, nós vamos começar aprendendo sobre como criar funções básicas em Go, seguido por métodos mais avançados e que estão correlacionados a criação de funções.
Criando seu projeto de testes
Dentro da pasta JornadaGoLang, nós iremos criar uma nova pasta chamada de 9-funcoes-em-go
, onde dentro dela, vamos criar o nosso arquivo main.go
:
package main
func main(){
}
Feito isso, vamos entrar de cabeça nesse mundo de funções 😉
Criando sua primeira função em Go
Como você já sabe, nós temos a função main()
que representa o ponto de partida da nossa aplicação.
Fazendo com que o compilador da linguagem sempre vá em busca dela para seguir as próximas instruções que declaramos.
Entretanto, a main()
não é, e nunca foi a ÚNICA FUNÇÃO que um programa feito em Go poderia ter.
E sim, você pode inúmeras funções em GoLang, desde que elas não sejam chamadas de main()
e não possuam o mesmo nome, porque caso contrário, isso gerará um erro de compilação.
Gosto de pensar em funções como caixinhas que contém blocos de códigos e que podem ser chamadas de maneira avulsa durante a execução do nosso código (ou nunca serem chamadas, caso desejar).
A sintaxe de uma função em GoLang é bem simples:
func NomeDaFuncao(parametro1 Tipo, parametro2 Tipo) TipoDeRetorno {
// Corpo da função (lógica de execução)
return valor
}
Além disso é importante ressaltar que funções em GoLang são consideradas First-Class Citizens, e isso significa que:
- Elas podem ser armazenadas dentro de variáveis,
- Elas podem ser passadas como parâmetros de outras funções,
- Elas podem ser retornadas por outras funções (uma função dentro da outra).
Agora vejamos um exemplo de uma função básica em GoLang:
package main
import "fmt"
// Declaração de uma função simples
func saudacao() {
fmt.Println("Olá, Mundo!")
}
func main() {
saudacao()
}
Observação: você pode ter funções dentro de outras funções, como é o caso de criar uma função dentro da função principal (main
), entretanto, é recomendável que essas funções estejam na mesma hierarquia da função principal do seu programa, ou seja, fora da main()
, mas dentro do arquivo .go
.
No comando acima, nós criamos uma função chamada de saudacao
, que executa uma mensagem de boas vindas.
Além disso, você também acabou de aprender a como chamar uma função no GoLang, bastando apenas citá-la dentro da função main()
:
saudacao()
É importante ressaltar que se a função saudacao()
não tivesse sido chamada dentro de main()
, a mesma nunca seria executada, por exemplo:
package main
import "fmt"
// Declaração de uma função simples
func saudacao() {
fmt.Println("Olá, Mundo!")
}
func main() {
}
Note que apesar da função ter sido declarada, a mesma nunca está sendo chamada.
Resumindo, uma função é composta de:
func
: Palavra-chave usada para definir uma função.saudacao
: Nome da função (segue a convenção de camelCase).main()
: Função principal que executa o programa.
Sendo assim, sempre que você for criar uma nova função em GoLang, certifique-se de usar o comando reservado func
seguido do nome da sua função.
Funções com parâmetros
Os parâmetros de uma função são chamados de argumentos da função.
Eles são passados por dentro dos parênteses da função, e é como declarar variáveis ali dentro, veja como é simples:

Note que o parâmetro da função nada mais é do que uma variável que só existe dentro daquele escopo. Vamos ver um exemplo:
package main
import "fmt"
// Declaração de uma função simples
func saudacao(nome string) {
fmt.Println("Olá: " + nome)
}
func main() {
saudacao("Micilini")
saudacao("Roll")
}
Note que no exemplo acima, nós criamos uma função chamada saudacao
, que recebe por parâmetro uma string
, que por sua vez, é armazenada dentro da variável nome
, e é mostrara no terminal em uma mensagem de boas vindas.
Note também, que as variáveis que declaramos dentro da nossa função, ficam disponíveis para acesso e até mesmo alteração de dentro do escopo daquela função.
É importante ressaltar que quando você cria uma função com um determinado parâmetro da forma como fizemos, ele se torna obrigatório. Isso quer dizer que sempre que você for chamar a função saudacao
, você é obrigado a informar uma string
. Caso contrário, isso pode gerar um erro no compilador.
Funções com parâmetros opcionais
Diferente de outras linguagens de programação, no GoLang não existe suporte nativo para parâmetros opcionais. O que nos obriga a informar todos os parâmetros que uma determinada função precisa receber.
Porém, existem algumas formas comuns de simular parâmetros opcionais em Go:
Variadic (...tipo)
: Quando há um ou mais parâmetros opcionais.Struct
: Para funções com muitos parâmetros opcionais.Ponteiros (*tipo)
: Quando um valor opcional pode estar ausente.Interface vazia (interface{})
: Para aceitar qualquer tipo de parâmetro.
Em lições futuras, iremos aprender a criar funções usando cada um deles 😉
Funções com mais de um parâmetro
Também é possível criar uma função em GoLang que receba mais de um único parâmetro, veja como é simples:
package main
import "fmt"
// Função com vários parâmetros
func detalhes(nome string, idade int, cidade string, ativo bool) {
fmt.Printf("Nome: %s, Idade: %d, Cidade: %s, Ativo: %t\n", nome, idade, cidade, ativo)
}
func main() {
detalhes("Gabriel", 34, "São Paulo", true)
}
Observação: Em geral, tente manter 3 a 5 parâmetros como um bom limite prático.
Funções com retornos em Go
As funções não servem apenas como "subprogramas" que executam um bloco de código e fica por isso mesmo.
Também é possível chamar uma função e recuperar o valor que é retornado por ela. Para isso, usamos o comando return
em conjunto com o tipo de retorno declarado antes da primeira chave da função, observe:
package main
import "fmt"
// Função que retorna um valor inteiro
func soma(a int, b int) int {
return a + b
}
func main() {
resultado := soma(5, 7)
fmt.Println("Resultado da soma:", resultado)
}
Note que na função soma
, recebemos dois parâmetros do tipo int
, e informamos que o retorno será um int
também.
Toda e qualquer função de retorno precisa usar o comando return
, indicando que algo está sendo retornando.
Além disso, o tipo de valor a ser retornado (no return) deve ser do mesmo tipo declarado na função.
É importante ressaltar que quando o return
é executado, todo e qualquer código existente abaixo dele, não será executado pelo Go, pois o comando return
faz a função terminar ali, ignorando tudo o que vem abaixo dele.
package main
import "fmt"
// Função que retorna um valor inteiro
func soma(a int, b int) int {
return a + b
fmt.Println("Isso nunca será executado!!!")
}
func main() {
resultado := soma(5, 7)
fmt.Println("Resultado da soma:", resultado)
}
Além disso, o GoLang permite que você retorne múltiplos valores em uma única função, observe:
package main
import "fmt"
// Função que retorna múltiplos valores
func dividir(dividendo, divisor int) (int, int) {
quociente := dividendo / divisor
resto := dividendo % divisor
return quociente, resto
}
func main() {
q, r := dividir(10, 3)
fmt.Printf("Quociente: %d, Resto: %d\n", q, r)
}
- Múltiplos valores são retornados entre parênteses (
int
,int
). - A função
dividir
retornaquociente
eresto
. - Os valores são atribuídos usando
:=
(declaração curta).
Observação: para declarar uma função sem retorno, basta não declarar o tipo de retorno durante a declaração da função.
Ignorando valores de retorno
Em GoLang, você pode ignorar os valores de retorno de uma função de duas formas principais:
✅ Ignorando todos os valores de retorno:
Se você não precisa de nenhum valor retornado por uma função (que retorna algo), basta chamar tal função sem a necessidade de armazenar o seu resultado, observe:
package main
import "fmt"
// Função que retorna um valor
func soma(a, b int) int {
return a + b
}
func main() {
soma(10, 5) // Ignorando o retorno
fmt.Println("Apenas executei a função!")
}
Note que a função soma
retorna a adição de duas variáveis, mas perceba que em momento nenhum eu estou recuperando aquele valor que foi retornado.
✅ Ignorando valores específicos com o identificador de descarte (_)
Se uma função retorna múltiplos valores, você pode ignorar um ou mais deles usando o underscore (_
), observe:
package main
import "fmt"
// Função que retorna dois valores
func dividir(a, b int) (int, error) {
if b == 0 {
return 0, fmt.Errorf("divisão por zero")
}
return a / b, nil
}
func main() {
resultado, _ := dividir(10, 2) // Ignorando o erro
fmt.Println("Resultado:", resultado)
}
Note que não é possível ignorar sem o uso do underscore quando a função tem mais de um retorno, pois isso gera um erro no compilador.
Além disso, você pode ignorar quantos valores de retorno você desejar, observe:
package main
import "fmt"
// Função que retorna três valores
func dados() (int, string, bool) {
return 42, "GoLang", true
}
func main() {
_, _, _ = dados() // Ignorando todos os valores
fmt.Println("Valores ignorados!")
}
E isso também vale para funções nativas das bibliotecas do GoLang:
package main
import "fmt"
func main() {
_, _ = fmt.Println("Olá, Go!") // Ignorando o número de bytes e erro
}
🧠 Dica: Ignorar erros pode ser perigoso. Sempre trate erros em operações críticas para evitar falhas silenciosas.
Declarando funções dentro de variáveis
Uma das possibilidades do GoLang, é a capacidade de declarar funções dentro de variáveis, e chamar essas mesmas variáveis como se fossem funções.
Esse ato também é conhecido como uma função anônima.
Cuja finalidade é criar uma função como se ela fosse um objeto comum. Vejamos como essas funções se comportam:
package main
import "fmt"
func main() {
dobro := func(x int) int {
return x * 2
}
fmt.Println("O dobro de 5 é:", dobro(5))
}
Perceba que no caso dessas funções, nós não precisamos declarar o nome da função em si, pois o próprio nome da variável já é considerado o nome da função.
Funções anônimas também podem não receber nenhum tipo de argumento, e muito menos não retornar nenhum valor, vejamos um exemplo:
package main
import "fmt"
func main() {
boasVindas := func() {
fmt.Println("Olá, mundo!!!");
}
boasVindas()
}
É importante ressaltar que funções anônimas são úteis em closures ou para serem usadas em lógicas temporárias.
É possível declarar uma função dentro de uma outra função?
Sim, em Go é possível declarar uma função dentro de outra! Essas funções internas são chamadas de funções anônimas (ou funções locais) e só podem ser usadas dentro do escopo da função onde foram declaradas.
Vejamos um pequeno exemplo:
package main
import "fmt"
func main() {
// Função declarada dentro do main()
saudacao := func(nome string) {
fmt.Printf("Olá, %s!\n", nome)
}
// Chamando a função interna
saudacao("William")
saudacao("Ana")
}
Note que declaramos uma função anônima que existe dentro da variável saudacao
, que é responsável por receber uma string
. Tudo isso dentro da função main()
😯
Funções nomeadas (Named Return Values)
O GoLang possuí uma funcionalidade muito interessante chamada de funções nomeadas, em que os valores retornados já estão previamente declarados no topo da função, observe:
package main
import "fmt"
// Função com retorno nomeado
func estatisticas(numeros []int) (soma int, quantidade int) {
for _, n := range numeros {
soma += n
}
quantidade = len(numeros)
return
}
func main() {
nums := []int{1, 2, 3, 4, 5}
s, q := estatisticas(nums)
fmt.Printf("Soma: %d, Quantidade: %d\n", s, q)
}
Note que dentro da função estatisticas
, nós temos duas variáveis como: soma
e quantidade
, que são justamente os valores que serão processados dentro do escopo da função e que serão retornados pelo return
.
Funções variádicas
Também no GoLang, é possível recebermos um número indefinido de argumentos. O que seria equivalente a funções com múltiplos parâmetros:
package main
import "fmt"
// Função variádica
func somar(numeros ...int) int {
total := 0
for _, n := range numeros {
total += n
}
return total
}
func main() {
fmt.Println(somar(1, 2, 3, 4, 5)) // Saída: 15
}
...int
indica que a função aceita múltiplos argumentos do tipo int
. De resto, usamos um slice
para capturar os diversos outros parâmetros que a função pode ter recebido.
Fique tranquilo, pois na próxima lição, você irá aprender tudo que precisa saber sobre slices
😉
Passando funções como parâmetros de outras funções
Sim, foi isso mesmo que você acabou de ler, o GoLang também permite que você passe uma função como um parâmetro de outra, vejamos como isso é feito:
package main
import "fmt"
// Função que aceita outra função como parâmetro
func executar(fn func(int, int) int, a int, b int) {
resultado := fn(a, b)
fmt.Println("Resultado:", resultado)
}
func main() {
soma := func(x, y int) int { return x + y }
executar(soma, 3, 4)
}
Note que dentro da função main()
, nos criamos uma função anônima, que foi passada para a função executar()
, que por sua vez, executa a função anônima dentro dela.
Interessante não? 🤓
Entendendo Closures em Go
Na linguagem GoLang, nós temos os famosos closures ou também conhecidos como fechamentos, que nada mais são do que funções que capturam e mantêm o acesso a variáveis externas ao seu escopo.
Isso significa que uma função interna pode lembrar e utilizar valores mesmo depois que o escopo externo onde foram definidos já tenha sido executado.
Para entendermos melhor, vamos ver um exemplo:
package main
import "fmt"
func contador() func() int {
// Variável no escopo da função externa (persistirá entre as chamadas)
contador := 0
// Função anônima (closure) que acessa a variável "contador"
return func() int {
contador++
return contador
}
}
func main() {
incrementar := contador()
fmt.Println(incrementar()) // Saída: 1
fmt.Println(incrementar()) // Saída: 2
fmt.Println(incrementar()) // Saída: 3
}
Começando pela função contador()
, nos criamos uma variável local chamada de contador
que é inicializada com o valor 0
. Além disso, note que o retorno da função contador, por si só, é uma função que retorna um inteiro 😆
Ainda nesta função, nós criamos uma função anônima que é responsável por incrementar e retornar o valor dessa variável.
Já quando executamos o comando incrementar := contador()
, nós estamos armazenando essa closure na variável incrementar, ao mesmo tempo que a função interna é responsável por lembrar o valor da variável contador
.
Fazendo com que a cada vez que chamarmos a função incrementar()
, o valor seja atualizado e preservado.
Observação: Mesmo que a função contador()
tenha terminado sua execução, a variável contador ainda está acessível pela função interna.
Além disso, podemos criar closures com múltiplas instâncias, observe:
package main
import "fmt"
func contador() func() int {
contador := 0
return func() int {
contador++
return contador
}
}
func main() {
// Dois closures independentes
c1 := contador()
c2 := contador()
fmt.Println(c1()) // Saída: 1
fmt.Println(c1()) // Saída: 2
fmt.Println(c2()) // Saída: 1 (novo closure, contador começa em 0)
}
No comando acima, cada chamada a uma função gera um closure criando um novo ambiente de execução, contento seu próprio estado.
✅ Por que isso acontece? Cada vez que contador()
é chamado, um novo contexto de variável contador
é criado. Os closures são independentes e mantêm estados separados.
Caso desejar, você também pode passar parâmetros para dentro dos seus closures, observe:
package main
import "fmt"
func multiplicador(fator int) func(int) int {
return func(valor int) int {
return valor * fator
}
}
func main() {
dobrar := multiplicador(2)
triplicar := multiplicador(3)
fmt.Println(dobrar(5)) // Saída: 10 (5 * 2)
fmt.Println(triplicar(5)) // Saída: 15 (5 * 3)
}
⚠️ Cuidados ao usar closures em Go:
- Closures não são automaticamente thread-safe. Se forem usados em
goroutines
, proteja as variáveis compartilhadas comsync.Mutex
ou utilize canais. - Se um closure mantiver referência a grandes estruturas (ex.: slices, maps), essas estruturas não serão liberadas até o closure ser descartado.
package main
import (
"fmt"
"sync"
)
func main() {
contador := 0
var wg sync.WaitGroup
incrementar := func() {
defer wg.Done()
contador++
}
for i := 0; i < 10; i++ {
wg.Add(1)
go incrementar() // Problema: corrida de dados!
}
wg.Wait()
fmt.Println("Contador final:", contador) // Resultado imprevisível!
}
Executando funções atrasadas com defer
O comando defer
em Go é usado para atrasar a execução de uma função até que o bloco atual (geralmente uma função) esteja prestes a terminar.
Em outras palavras, a função marcada com defer
será executada após a finalização de todo o código restante no bloco onde foi definida.
Para que você possa entender melhor, vamos começar com um exemplo simples usando mensagens no terminal:
package main
import "fmt"
func main() {
fmt.Println("Início")
defer fmt.Println("Defer 1")
defer fmt.Println("Defer 2")
fmt.Println("Fim")
}
A saída do código acima será:
Início
Fim
Defer 2
Defer 1
- fmt.Println("Início") → Executa imediatamente.
- defer fmt.Println("Defer 1") → Adia a execução até o final do
main()
. - defer fmt.Println("Defer 2") → Também é adiada.
- fmt.Println("Fim") → Executa antes dos
defer
.
É importante ressaltar também que os defer
são executados em ordem inversa à que foram definidos (LIFO - Last In, First Out).
Mas daí você pode estar se perguntando... porque usar um defer? E a reposta pode te surpreender 😉
- Ele libera recursos (ex: fechar arquivos ou conexões),
- Ele garante a execução de código crítico (ex: logging de ou recuperação de pânico),
- Ele melhora a legibilidade ao deixar a lógica de limpeza próxima ao recurso aberto.
Entendendo sobre a pilha de funções (Call Stack) em Go
A pilha de funções em Go (e em outras linguagens) é uma estrutura de dados LIFO (Last In, First Out – "Último a Entrar, Primeiro a Sair) que armazena informações sobre chamadas de funções durante a execução do programa.
Cada vez que uma função é chamada, um Frame (registro) é empilhado com informações como:
- Parâmetros da função.
- Variáveis locais.
- Endereço de retorno (para onde voltar ao finalizar a função).
Em seguida, após a finalização desta função, seu frame é desempilhado e o controle retorna para a função anterior.
Vamos ver um exemplo básico disso funcionando:
package main
import "fmt"
func funcA() {
fmt.Println("Executando funcA")
funcB()
fmt.Println("Finalizando funcA")
}
func funcB() {
fmt.Println("Executando funcB")
funcC()
fmt.Println("Finalizando funcB")
}
func funcC() {
fmt.Println("Executando funcC")
}
func main() {
fmt.Println("Início do programa")
funcA()
fmt.Println("Fim do programa")
}
A saída no terminal será:
Início do programa
Executando funcA
Executando funcB
Executando funcC
Finalizando funcB
Finalizando funcA
Fim do programa
Para explicar melhor do que aconteceu ali, vamos analisar os seguintes pontos:
- main() → Primeiro frame (ponto de entrada da aplicação).
- funcA() → É chamada e empilhada.
- funcB() → É chamada por
funcA()
e empilhada. - funcC() → É chamada por
funcB()
e empilhada. - Ao finalizar
funcC()
, ela é removida (desempilhada) e o controle volta parafuncB()
. - Ao finalizar
funcB()
, ela é removida e volta parafuncA()
. - Por fim, volta para
main()
e o programa termina.
Apesar disso, quando ocorre um pânico (panic) em Go, o compilador de maneira automática imprime a pilha de chamadas para ajudar a localizar a origem do erro.
Além disso, o Go oferece o pacote runtime/debug
, que contém a função PrintStack()
para imprimir a pilha de chamadas (stack trace) em qualquer ponto do programa.
package main
import (
"fmt"
"runtime/debug"
)
func rastrear() {
fmt.Println("📌 Pilha de chamadas atual:")
debug.PrintStack()
}
func funcA() {
funcB()
}
func funcB() {
funcC()
}
func funcC() {
rastrear() // Exibe a pilha
}
func main() {
funcA()
}
Veja como ficou a saída:
📌 Pilha de chamadas atual:
goroutine 1 [running]:
runtime/debug.Stack(...)
/usr/local/go/src/runtime/debug/stack.go:24
main.rastrear()
/caminho/do/arquivo.go:10
main.funcC()
/caminho/do/arquivo.go:18
main.funcB()
/caminho/do/arquivo.go:14
main.funcA()
/caminho/do/arquivo.go:10
main.main()
/caminho/do/arquivo.go:26
Com o PrintStack()
somos capazes de analisar toda a pilha de funções que estão sendo chamadas, isso ajuda a debugar o código 😉
Principais diferenças entre funções e estruturas condicionais
A grande diferença entre as funções das estruturas condicionais e iterações, é que no caso das funções, o Go passa direto por elas, sem executa-las.
Isso quer dizer que se você declarar uma função, e não chama-la, o conteúdo ali existente nunca será executado.
package main
import "fmt"
// Declaração de uma função simples
func saudacao() {
fmt.Println("Olá, Mundo!")
}
func main() {
}
Isso é o que difere as funções das estruturas condicionais e iterações, enquanto essas estruturas do GoLang, elas meio que tentam fazer a execução antes de entrar no bloco, no caso das funções ele nem mesmo tenta fazer a execução delas (ao menos que exista um código que chame esta função).
E é por esse motivo que as funções são consideradas como um "subprograma".
Entendendo recursividade em Go
No GoLang, nós temos um conceito chamado de recursividade, onde uma determinada função consegue chamar a si própria.
Entretanto, se uma função pode chamar a si mesma, é como se tivéssemos um loop infinito (famoso stackoverflow, ou mais conhecido como estouro de pilhas), portanto, também é necessário que entendamos como parar tal ciclo 😉
Basicamente, o conceito de recursividade acontece quando uma função chama a si mesma, direta ou indiretamente, para resolver um problema. É uma técnica útil para dividir problemas complexos em partes menores e mais gerenciáveis.
Vejamos alguns exemplos matemáticos:
📌 Exemplo 1: Fatorial de um número (Recursão Simples)
O fatorial de um número 𝑛 é o produto de todos os números inteiros positivos de 1 até 𝑛:

Em termos recursivos, nos teríamos essa formula:

Para representar isso em Go, teríamos o seguinte:
package main
import "fmt"
// Função recursiva para calcular o fatorial
func fatorial(n int) int {
// Caso base: fatorial de 0 ou 1 é 1
if n == 0 || n == 1 {
return 1
}
// Passo recursivo: n * fatorial(n - 1)
return n * fatorial(n-1)
}
func main() {
fmt.Println("Fatorial de 5 é:", fatorial(5))
}
A saída desse programa será:
Fatorial de 5 é: 120
No caso da lógica acima, a execução "desempilha" esses valores na volta, multiplicando-os.
📌 Exemplo 2: Sequência de Fibonacci (Recursão Simples)
Já quando queremos simular a sequência de fibonacci, temos que pensar na seguinte formula:

Representando isso em Go, ficaria da seguinte forma:
package main
import "fmt"
// Função recursiva para calcular o n-ésimo termo da sequência de Fibonacci
func fibonacci(n int) int {
// Casos base: F(0) = 0, F(1) = 1
if n == 0 {
return 0
}
if n == 1 {
return 1
}
// Passo recursivo: F(n) = F(n-1) + F(n-2)
return fibonacci(n-1) + fibonacci(n-2)
}
func main() {
for i := 0; i <= 10; i++ {
fmt.Printf("Fibonacci(%d) = %d\n", i, fibonacci(i))
}
}
A saída será:
Fibonacci(0) = 0
Fibonacci(1) = 1
Fibonacci(2) = 1
Fibonacci(3) = 2
Fibonacci(4) = 3
Fibonacci(5) = 5
Fibonacci(6) = 8
Fibonacci(7) = 13
Fibonacci(8) = 21
Fibonacci(9) = 34
Fibonacci(10) = 55
Nota: Esse código é ineficiente para valores altos, pois recalcula os mesmos resultados várias vezes. Para maior eficiência, utilize memoização ou iteratividade.
📌 Exemplo 3: Somatório Recursivo (Recursão Simples)
Se você deseja calcular a soma de todos os números de 1
até n
, você pode fazer isso da seguinte forma:

Representando isso em Go:
package main
import "fmt"
// Função recursiva para calcular a soma de 1 até n
func soma(n int) int {
if n == 0 {
return 0 // Caso base
}
return n + soma(n-1) // Passo recursivo
}
func main() {
fmt.Println("Soma de 1 até 10 é:", soma(10))
}
Veja como ficou a saída:
Soma de 1 até 10 é: 55
📌 Exemplo 4: Invertendo uma String (Recursão Simples)
package main
import "fmt"
// Função para inverter uma string recursivamente
func inverte(s string) string {
if len(s) == 0 {
return "" // Caso base
}
return string(s[len(s)-1]) + inverte(s[:len(s)-1]) // Passo recursivo
}
func main() {
fmt.Println("Resultado:", inverte("golang"))
}
Cuidados que devemos tomar ao utilizar recursividade em Go
Existem três pontos principais que você precisa tomar muito cuidado ao fazer o uso da recursividade no seu programa:
Stack Overflow: A cada chamada recursiva, um novo frame é adicionado à pilha. Se a profundidade for muito grande, ocorre um estouro de pilha.
Desempenho: A recursão pode ser menos eficiente que loops devido ao custo de empilhamento/desempilhamento.
Memoização: Para funções como Fibonacci, considere salvar resultados intermediários para evitar cálculos repetidos.
Funções Init
Por fim, nós temos as funções init
, que é uma função especial, usada para inicializar pacotes antes da execução da função main()
em programas que possuem múltiplos pacotes.
Ela é chamada automaticamente pelo Go, sem que você precise chamá-la explicitamente. O objetivo da função init
é permitir a configuração ou inicialização de estado necessário para que o programa funcione corretamente.
Vejamos algumas de suas características:
É chamada automaticamente: O Go chama todas as funções init
de forma automática, sem que você precise invocá-las explicitamente.
Não pode ter parâmetros nem valores de retorno: A função init
não recebe parâmetros, e também não retorna nenhum valor. Ela serve apenas para inicialização.
Ordem de execução: Se um pacote importar outros pacotes, as funções init
são chamadas na ordem de importação. Dentro de um mesmo pacote, as funções init
são chamadas na ordem em que são declaradas no código.
Agora chega de teoria, e vamos ver um exemplo na prática 😎
package main
import "fmt"
// Variável global
var contador int
// Função init para inicializar o contador
func init() {
contador = 10
fmt.Println("Função init chamada. Contador inicializado:", contador)
}
func main() {
fmt.Println("Dentro da função main. Contador:", contador)
}
No exemplo acima, a função init
inicializa uma variável global antes que o código da função main
seja executado:
Um outro exemplo muito comum do uso da função init
, envolve a abertura de comunicações com bases de dados:
package main
import "fmt"
// Função init para inicializar banco de dados
func init() {
fmt.Println("Inicializando conexão com o banco de dados...")
// Aqui, você poderia abrir uma conexão com um banco de dados real
}
func main() {
fmt.Println("Aplicação em execução.")
}
Além disso, você pode utilizar funções do tipo init
para configurar ou registrar recursos necessários para esse pacote:
package minhaBiblioteca
import "fmt"
// Função init para inicializar uma biblioteca
func init() {
fmt.Println("Biblioteca MinhaBiblioteca inicializada!")
}
E no main.go
:
package main
import (
"fmt"
"minhaBiblioteca"
)
func main() {
fmt.Println("Início do programa principal.")
}
Métodos construtores e funções assíncronas existem em Go?
Tecnicamente, Go não possui um construtor como em linguagens orientadas a objetos (como Java ou C#).
No entanto, a abordagem idiomática em Go é criar uma função que retorna uma nova instância de uma estrutura (struct
), simulando o comportamento de um construtor.
E isso pode ser realizado da seguinte maneira:
package main
import "fmt"
// Definindo uma struct
type Usuario struct {
Nome string
Idade int
}
// Função que age como um "construtor"
func NovoUsuario(nome string, idade int) *Usuario {
return &Usuario{Nome: nome, Idade: idade}
}
func main() {
u := NovoUsuario("William", 30)
fmt.Println(u)
}
Já com relação a funções assíncronas, o Go não possui comandos async/await
como visto em linguagens como JavaScript ou Python, mas lida com concorrência de maneira eficiente usando goroutines
.
🟢 Goroutine nada mais é do que uma função que é executada de forma assíncrona (em paralelo) em uma thread leve gerenciada pelo próprio GoLang.
Boas práticas durante a criação de funções em Go
Antes mesmo de declararmos uma função, nós precisamos nos certificar de alguns pontos:
1️⃣ Cada função deve ter um propósito bem definido
Sendo assim, evite funções genéricas ou que realizam múltiplas tarefas. Tenha em mente que se uma função fizer muitas coisas, ela não deve ser considerada uma função, mas sim um módulo.
Caso seja necessário, divida-a em funções menores e mais específicas, por exemplo, vamos supor que nós temos uma função responsável por processar um determinado usuário:
package main
import (
"fmt"
"strings"
)
// Função que realiza várias tarefas ao mesmo tempo
func processarUsuario(nome string, idade int) {
nome = strings.TrimSpace(nome) // Remove espaços em branco
nome = strings.Title(nome) // Capitaliza o nome
fmt.Printf("Nome formatado: %s\n", nome)
if idade >= 18 {
fmt.Println("Usuário é maior de idade.")
} else {
fmt.Println("Usuário é menor de idade.")
}
}
func main() {
processarUsuario(" ana clara ", 17)
}
Observação: existem alguns comandos novos que você ainda não aprendeu, mas fique tranquilo que em lições futuras você os verá 🙂
A função acima, possui os seguintes problemas, sendo o maior deles o fato da função processarUsuario
estar fazendo três coisas diferentes:
- Formatar um nome
- Verificar uma idade
- Exibir mensagens no console
Além é claro, da dificuldade da reutilização e a manutenção do mesmo.
Levando em consideração de que podemos quebrar essas funções em pequenas funções menores, nós poderíamos fazer isso da seguinte forma:
package main
import (
"fmt"
"strings"
)
// Função que formata o nome
func formatarNome(nome string) string {
nome = strings.TrimSpace(nome) // Remove espaços
return strings.Title(nome) // Capitaliza o nome
}
// Função que verifica a maioridade
func verificarMaioridade(idade int) string {
if idade >= 18 {
return "Usuário é maior de idade."
}
return "Usuário é menor de idade."
}
// Função principal que organiza as outras
func processarUsuario(nome string, idade int) {
nomeFormatado := formatarNome(nome)
fmt.Printf("Nome formatado: %s\n", nomeFormatado)
mensagem := verificarMaioridade(idade)
fmt.Println(mensagem)
}
func main() {
processarUsuario(" ana clara ", 17)
}
Note que cada função tem uma responsabilidade única, o que a torna mais fácil de às testar individualmente.
Além disso, as funções formatarNome()
ou verificarMaioridade()
podem ser usadas em outros contextos, evitando duplicidade de código.
2️⃣ Escolha nomes claros e descritivos
Lembre-se de que o nome da função deve exemplificar claramente o que ela faz.
Por exemplo, se você deseja criar uma função cujo o objetivo é calcular o valor do desconto sobre um determinado produto, em vez de você criar uma função chamada processar()
, você poderia ser mais específico, criando uma função chamada calcularDesconto()
.
3️⃣ Siga a convenção de nomenclatura
Quando estamos criando funções em GoLang, nós usamos o camelCase
, onde a primeira letra é sempre minúscula, e os termos seguintes começam com maiúscula.
🟢 Vejamos alguns exemplos:
obterUsuario()
,calcularDesconto()
,fecharAplicacao()
🔴 Evite fazer isso da seguinte forma:
Obter_usuario()
,obter_usuario()
,fechar_aplicaCao()
4️⃣ Evite nomes muito genéricos
Evite usar alguns nomes, tais como: executar()
, processar()
ou fazerAlgo()
uma vez que eles não indicam claramente a intenção daquela função.
5️⃣ Funções devem ser curtas e objetivas
Em um mundo ideal, uma função deve fazer apenas uma ÚNICA COISA.
Sendo assim, se você perceber que uma função está muito longa, e faz inúmeras coisas, repense em dividi-la em funções menores.
6️⃣ Evite efeitos colaterais inesperados
Uma função não deve modificar variáveis globais sem necessidade. Sendo assim, se mesmo assim ela precisar modificar algo externo, torne esse comportamento explícito no nome da função.
Por exemplo: mudaTokenGlobal()
.
7️⃣ Prefira múltiplos retornos a estruturas complexas
O Go permite que você retorne múltiplos valores, o que já nos facilita na manipulação de resultados, por exemplo:
func dividir(a, b int) (int, int) {
return a / b, a % b
}
8️⃣ Utilize defer para garantir a execução de limpeza de recursos
Quando estamos trabalhando com arquivos ou conexões com bases de dados, é recomendável que você utilize o comando defer
para isso:
func abrirArquivo(nome string) {
arquivo, _ := os.Open(nome)
defer arquivo.Close() // Fecha o arquivo ao final da função
}
9️⃣ Documente funções públicas
Se a sua função por exportada (iniciada com letra maiúscula), adicione um comentário explicando o seu propósito:
// CalcularDesconto aplica um desconto percentual a um valor e retorna o resultado.
func CalcularDesconto(valor float64, desconto float64) float64 {
return valor - (valor * desconto / 100)
}
🔟 Evite passar muitos parâmetros
Por fim, e não menos importante, se a sua função precisa de muitos parâmetros, considere agrupá-los em uma struct
, por exemplo:
//Maneira errada...
func criarPedido(nome string, idade int, endereco string, telefone string) {}
//Maneira certa...
type Pedido struct {
Nome string
Idade int
Endereco string
Telefone string
}
func criarPedido(p Pedido) {}
Além disso, repense novamente se a função não quebra a regra de número 5️⃣.
Repositório da lição
Todos os arquivos relacionados com esta lição, podem ser encontrados nos seguintes repositórios abaixo:
Conclusão
Ufa!!! 😱
Parece que você aprendeu diversas informações sobre funções em Go, não é mesmo? 🤭
Sendo assim, eu te recomendo dar uma respirada para que você faça a associação de todo conteúdo que aprendeu até aqui.
E quando estiver pronto, eu vou estar te aguardando na próxima lição...