Funções
As Funções são blocos de código criados para executar uma tarefa específica. Em Go, uma função é declarada com func, exemplo:
func nomeDaFuncao() {
// código da função
}
A função main, que já usamos antes, também é uma função:
func main() {
// ponto de entrada do programa
}
A diferença é que main tem um papel especial, quando o programa Go é executado, ele começa pela função main.
Criando uma função simples
Uma função simples pode não receber nada e não retornar nada.
package main
import "fmt"
func main() {
// Chama a função mostrarMensagem.
mostrarMensagem()
}
// Função sem parâmetros e sem retorno.
func mostrarMensagem() {
fmt.Println("Aprendendo funções em Go")
}
A chamada da função acontece aqui:
mostrarMensagem()
Os parênteses são obrigatórios, mesmo quando a função não recebe valores.
Parâmetros
Os parâmetros são valores que a função recebe para trabalhar.
func saudar(nome string) {
fmt.Println("Olá,", nome)
}
Nesse exemplo, nome é um parâmetro do tipo string. Ao chamar a função, passamos um valor:
saudar("Ana")
Exemplo mais completinho:
package main
import "fmt"
func main() {
// "Ana" é o valor enviado para a função.
saudar("Ana")
// "Carlos" é outro valor enviado para a mesma função.
saudar("Carlos")
}
// nome é um parâmetro.
// Ele recebe o valor passado na chamada da função.
func saudar(nome string) {
fmt.Println("Olá,", nome)
}
Resultado:
Olá, Ana
Olá, Carlos
Parâmetro e argumento
Essas duas palavras aparecem bastante. O parâmetro é o nome declarado na função, exemplo:
func saudar(nome string) {
}
Aqui, nome é o parâmetro. Já o argumento é o valor passado na chamada.
saudar("Ana")
Aqui, "Ana" é o argumento. Na prática, muita gente mistura os nomes, mas a diferença técnica é essa (não que faça muita diferença no dia a dia).
Mais de um parâmetro
Uma função pode receber mais de um parâmetro.
package main
import "fmt"
func main() {
mostrarDados("Ana", 20)
}
// A função recebe uma string e um int.
func mostrarDados(nome string, idade int) {
fmt.Println("Nome:", nome)
fmt.Println("Idade:", idade)
}
Quando vários parâmetros têm o mesmo tipo, Go permite escrever o tipo uma vez no final:
func somar(a, b int) {
fmt.Println(a + b)
}
Isso é igual a escrever:
func somar(a int, b int) {
fmt.Println(a + b)
}
Retorno de função
Uma função pode devolver um valor, para isso, colocamos o tipo do retorno depois dos parênteses.
func somar(a, b int) int {
return a + b
}
Nesse exemplo, a função recebe dois int e retorna um int (esse int depois dos parênteses é o tipo do retorno).
return
O return encerra a função e devolve um valor, quando a função tem retorno.
package main
import "fmt"
func main() {
resultado := verificarIdade(20)
fmt.Println(resultado)
}
func verificarIdade(idade int) string {
if idade >= 18 {
return "Maior de idade"
}
return "Menor de idade"
}
Quando a condição idade >= 18 é verdadeira, a função retorna "Maior de idade" e termina ali. Se a condição não for verdadeira, o primeiro return não acontece, então a função chega no segundo return.
Toda função com retorno precisa retornar
Se a função promete retornar um valor, todos os caminhos precisam devolver esse valor. Se nenhum valor for devolvido via return, seu código está errado.
Escopo de variáveis dentro de funções
As variáveis criadas dentro de uma função existem apenas dentro dela.
package main
import "fmt"
func main() {
criarNome()
// Vai dar erro, já que a variável 'nome' foi criada dentro de 'criarNome'.
// Ela não existe dentro de 'main'.
fmt.Println(nome)
}
func criarNome() {
nome := "Ana"
fmt.Println(nome)
}
Isso é chamado de escopo, ele indica a região do código onde uma variável existe e pode ser usada. Se uma função precisa usar um valor criado em outro lugar, o valor pode ser passado por parâmetro ou retornado.
Ordem das funções no arquivo
Em Go, a ordem em que as funções aparecem no arquivo não define se uma função pode ou não ser usada. Isso significa que uma função pode ser chamada antes de estar escrita no arquivo, desde que ela exista no mesmo pacote.
package main
import "fmt"
func main() {
// A função é chamada aqui.
mostrarMensagem()
}
// A função foi escrita depois da main,
// mas ainda pertence ao mesmo pacote.
func mostrarMensagem() {
fmt.Println("Olá")
}
Esse código funciona porque o compilador não lê o arquivo como se fosse executar linha por linha de cima para baixo. Antes de gerar o programa, ele analisa o pacote inteiro, identifica as funções declaradas e só depois monta o executável.
Por isso, main consegue chamar mostrarMensagem, mesmo que mostrarMensagem apareça mais abaixo no arquivo.
A ordem abaixo também funcionaria:
package main
import "fmt"
func mostrarMensagem() {
fmt.Println("Olá")
}
func main() {
mostrarMensagem()
}
Os dois exemplos são válidos, a diferença está apenas na organização do código, não no funcionamento.
Em projetos pequenos, é comum deixar main primeiro, porque ela mostra rapidamente onde o programa começa. Em outros casos, algumas pessoas preferem colocar funções auxiliares antes ou depois, dependendo da leitura do arquivo.
Função init
A função init é uma função especial que o Go executa automaticamente durante a inicialização de um programa. Ela é usada para preparar um pacote antes que o restante do programa comece a funcionar.
A função init deve ter exatamente esta forma:
func init() {
// Código de inicialização
}
Ela não recebe parâmetros e não retorna valores. A função init roda sempre antes da função main.
Retorno múltiplo
Em Go, uma função pode retornar mais de um valor. Isso é usado quando uma função precisa devolver um resultado principal e alguma informação extra, como um erro, um status ou outro dado calculado junto.
package main
import "fmt"
func main() {
soma, dobro := calcular(10, 5)
fmt.Println("Soma:", soma)
fmt.Println("Dobro:", dobro)
}
// A função retorna dois valores do tipo int.
func calcular(a, b int) (int, int) {
soma := a + b
dobro := soma * 2
return soma, dobro
}
Resultado:
Soma: 15
Dobro: 30
A parte importante está aqui:
func calcular(a, b int) (int, int)
Como a função retorna mais de um valor, os tipos de retorno ficam entre parênteses.
(int, int)
Isso significa que essa função retorna dois valores, e os dois são int.
Tratamento de erros
Em Go, erros são tratados de forma explícita. Nós não usamos try/catch como algumas linguagens, o padrão mais comum é uma função retornar um valor e um erro.
A ideia normalmente é esta:
resultado, err := algumaFuncao()
if err != nil {
// tratar o erro
}
O err é uma variável que representa o erro e o nil significa ausência de valor. No caso de erro, err == nil quer dizer que não houve erro. Se err for diferente de nil, aconteceu algum erro.
Criando uma função que pode retornar erro
Para criar erros simples, podemos usar o pacote errors.
package main
import (
"errors"
"fmt"
)
func main() {
resultado, err := dividir(10, 0)
if err != nil {
fmt.Println("Erro:", err)
return
}
fmt.Println("Resultado:", resultado)
}
// A função retorna um int e um error.
func dividir(a, b int) (int, error) {
if b == 0 {
// Retorna 0 como valor padrão e um erro explicando o problema.
return 0, errors.New("não é possível dividir por zero")
}
// Quando não há erro, retornamos o resultado e nil.
return a / b, nil
}
Resultado:
Erro: não é possível dividir por zero
A assinatura da função é:
func dividir(a, b int) (int, error)
Isso significa que ela retorna dois valore, o primeiro é o resultado da divisão e o segundo é um erro, caso algo dê errado.
Criando erro com fmt.Errorf
Também dá para criar erros com fmt.Errorf. Ele é útil quando queremos colocar valores dentro da mensagem.
package main
import (
"fmt"
)
func main() {
idade := -5
err := validarIdade(idade)
if err != nil {
fmt.Println("Erro:", err)
return
}
fmt.Println("Idade válida")
}
func validarIdade(idade int) error {
if idade < 0 {
// fmt.Errorf permite montar uma mensagem com valor.
return fmt.Errorf("idade inválida: %d", idade)
}
return nil
}
Resultado:
Erro: idade inválida: -5
Aqui a função retorna apenas error.
func validarIdade(idade int) error
Isso faz sentido porque a função não precisa devolver um resultado principal. Ela só precisa dizer se a idade é válida ou se houve erro.
defer
O defer é usado para adiar a execução de uma função até o final da função atual. Ele é muito usado para garantir que alguma limpeza será feita, como fechar arquivo, fechar conexão, liberar lock ou registrar algo no final da execução.
Exemplo simples
package main
import "fmt"
func main() {
fmt.Println("Início")
// Esta linha será executada no final da função main.
defer fmt.Println("Executando defer")
fmt.Println("Fim")
}
Resultado:
Início
Fim
Executando defer
Mesmo que o defer apareça antes de fmt.Println("Fim"), ele só executa quando a função main está terminando.
O defer existe para evitar esquecer ações de finalização. Por exemplo, quando abrimos um arquivo, normalmente precisamos fechar esse arquivo depois. Sem defer, teríamos que lembrar de fechar manualmente em todos os caminhos possíveis da função.
Com defer, podemos abrir o recurso e logo em seguida registrar o fechamento. A ideia fica assim:
arquivo, err := os.Open("dados.txt")
if err != nil {
return err
}
defer arquivo.Close()
Mesmo que a função tenha vários return, o defer ainda será executado antes da função sair.
Exemplo com função separada
package main
import "fmt"
func executar() {
fmt.Println("Abrindo recurso")
// Será executado quando executar() terminar.
defer fmt.Println("Fechando recurso")
fmt.Println("Usando recurso")
}
func main() {
executar()
}
Resultado:
Abrindo recurso
Usando recurso
Fechando recurso
O defer está dentro da função executar, então ele roda quando executar termina, não necessariamente quando o programa inteiro termina.