Declarando uma função
Uma declaração de função é func, um nome, os parâmetros, o tipo do resultado e um corpo. O tipo vem depois do nome do parâmetro, não antes.
add recebe dois valores int e devolve um int. greet não devolve nada, então a assinatura não tem tipo de resultado e o corpo não precisa de return.
Uma função que declara um resultado precisa terminar em uma instrução de encerramento. Se algum caminho puder chegar à chave de fechamento sem retornar, o compilador para com missing return.
Parâmetros que compartilham o tipo
Parâmetros consecutivos do mesmo tipo podem compartilhar um único nome de tipo. func add(a, b int) int significa exatamente o mesmo que func add(a int, b int) int. Misturar também é permitido:
func scale(x, y float64, factor int) (float64, float64)
Aqui x e y são float64 e factor é um int.
Valores de retorno
Uma função pode devolver zero, um ou vários valores. Vários resultados vão entre parênteses, e quem chama os recebe com uma atribuição múltipla:
Isto imprime 3 2. O par (valor, error) é o uso mais comum desse recurso em Go, e ele tem uma página própria sobre múltiplos valores de retorno.
Argumentos são copiados
Go passa todo argumento por valor. A função trabalha em uma cópia, então atribuir a um parâmetro nunca muda a variável de quem chama.
A saída é:
{1 2}
{100 2}
[99 2 3]
A terceira linha surpreende. Um valor de slice é um cabeçalho pequeno (ponteiro, tamanho, capacidade). A cópia aponta para o mesmo array subjacente, então escrever em s[0] escreve no elemento de quem chama. Mas um append dentro da função pode alocar um array novo, e quem chama nunca vê esse cabeçalho novo. Se uma função aumenta um slice, devolva o slice novo. A mesma lógica vale para maps: a função pode acrescentar chaves que quem chama enxerga, porque o valor do map aponta para dados compartilhados.
Use um parâmetro ponteiro quando a função precisar modificar a variável de quem chama, ou quando o valor for uma struct grande que você não quer copiar a cada chamada.
Funções são valores
Uma função tem um tipo, escrito sem o nome: func(int, int) int. Você pode guardar uma função em uma variável, colocá-la em um map ou slice e passá-la para outra função.
square é uma função anônima atribuída a uma variável. Funções anônimas também podem ler e alterar variáveis do escopo ao redor, o que as torna closures.
Um tipo de função nomeado deixa as assinaturas mais legíveis quando o mesmo formato aparece em muitos lugares:
type Transform func(int) int
func apply(xs []int, f Transform) []int
O valor zero de um tipo de função é nil. Chamar uma função nil causa panic com invalid memory address or nil pointer dereference, então verifique callbacks opcionais antes de chamá-los. Valores de função só podem ser comparados com nil, nunca entre si.
Recursão
Uma função pode chamar a si mesma. Go não tem otimização de chamada de cauda, mas as pilhas das goroutines crescem sob demanda, então profundidades comuns de recursão não são problema.
for i := range 6 conta de 0 a 5 e exige o Go 1.22 ou mais recente.
O que as funções em Go não têm
- Sem sobrecarga. Duas funções no mesmo pacote não podem ter o mesmo nome, nem com parâmetros diferentes.
- Sem argumentos padrão ou nomeados. Todo parâmetro é passado em toda chamada, na ordem. Uma struct de configuração dá campos nomeados e opcionais:
NewServer(Config{Port: 8080}). - Sem funções nomeadas aninhadas. Dentro do corpo de uma função você só pode declarar funções anônimas, normalmente atribuídas a uma variável.
Listas de argumentos de tamanho variável são suportadas por meio de funções variádicas.
Funções exportadas e não exportadas
Uma função cujo nome começa com letra maiúscula (Parse) é exportada e pode ser chamada de outros pacotes. Um nome com minúscula (parse) só é visível dentro do próprio pacote. Não existem as palavras-chave public ou private; a letra inicial é a regra inteira.
Erros comuns
- Esperar que a mudança em um parâmetro chegue a quem chama. Atribuir a um parâmetro só muda a cópia. Devolva o valor novo ou receba um ponteiro.
- Fazer append dentro de uma função e ignorar o resultado.
func add(s []int) { s = append(s, 1) }não afeta o tamanho do slice de quem chama. Escrevafunc add(s []int) []inte uses = add(s). - Resultados ignorados. Go permite ignorar um valor de retorno. O
go vetnão aponta um erro descartado, mas linters como oerrcheck(parte dogolangci-lint) apontam. Use_ =só quando ignorar for uma decisão.
Perguntas frequentes
Como definir uma função em Go?
Use a palavra-chave func, um nome, uma lista de parâmetros com os tipos depois dos nomes e o tipo do resultado: func add(a, b int) int { return a + b }. Uma função sem resultado omite o tipo, e uma função com vários resultados os lista entre parênteses: func divmod(a, b int) (int, int).
Go passa argumentos por valor ou por referência?
Sempre por valor. A função recebe uma cópia de cada argumento. Para um int ou uma struct, essa cópia é independente da variável de quem chama. Slices, maps, channels e ponteiros são valores pequenos que apontam para dados compartilhados, então mudanças nos elementos apontados ficam visíveis para quem chama, enquanto reatribuir o próprio parâmetro não fica. Passe um ponteiro (*T) quando a função precisar mudar a variável de quem chama.
Dá para passar uma função como parâmetro em Go?
Sim. Funções são valores com um tipo, como func(int) int. Declare um parâmetro desse tipo e passe qualquer função com assinatura compatível, nomeada ou anônima: func apply(xs []int, f func(int) int).
Go tem sobrecarga de funções ou parâmetros padrão?
Não. Cada nome de função em um pacote precisa ser único, e todo parâmetro precisa ser passado em toda chamada. Os substitutos comuns são nomes distintos (NewServer, NewServerWithTLS), uma struct de configuração, parâmetros variádicos ou functional options.