Uma função permite agrupar código em uma unidade reutilizável.
Ela é composta pela palavra-chave func, pelo nome da função e por uma lista, separada por vírgulas, de zero ou mais parâmetros e tipos entre parênteses.
Todos os parâmetros precisam ter o tipo declarado explicitamente; não há inferência de tipo para parâmetros. Não existem valores padrão para parâmetros, então todos os parâmetros de uma função são obrigatórios.
import "fmt"
// No parameters
func PrintHello() {
fmt.Println("Hello")
}
// One parameter
func PrintHelloName(name string) {
fmt.Println("Hello " + name)
}
Parâmetros do mesmo tipo podem ser declarados juntos, seguidos de uma única declaração de tipo.
import "fmt"
func PrintGreetingName(greeting, name string) {
fmt.Println(greeting + " " + name)
}
Vamos ver rapidamente dois termos que costumam ser confundidos: parameters e arguments.
Os parâmetros de uma função são os nomes definidos na assinatura da função, como greeting e name na função PrintGreetingName acima.
Os argumentos de uma função são os valores concretos passados aos parâmetros da função quando invocamos a função.
Por exemplo, no exemplo abaixo, "Hello" e "Katrina" são os argumentos passados aos parâmetros greeting e name:
PrintGreetingName("Hello", "Katrina")
Os parâmetros da função são seguidos de zero ou mais valores de retorno, que também precisam ter o tipo declarado explicitamente.
Valores de retorno únicos ficam sem parênteses; múltiplos valores de retorno ficam entre parênteses.
Os valores são retornados ao código que chamou a função usando a palavra-chave return.
Pode haver várias instruções return em uma função.
A execução da função termina assim que ela encontra uma dessas instruções return.
Se uma função precisar retornar vários valores, eles ficam separados por vírgulas.
Você encontra mais informações sobre o uso idiomático de múltiplos valores de retorno no conceito vinculado.
func Hello(name string) string {
return "Hello " + name
}
func HelloAndGoodbye(name string) (string, string) {
return "Hello " + name, "Goodbye " + name
}
Para chamar uma função, você especifica o nome dela e passa argumentos para cada um dos parâmetros da função entre parênteses.
import "fmt"
// No parameters, no return value
func PrintHello() {
fmt.Println("Hello")
}
// Called like this:
PrintHello()
// One parameter, one return value
func Hello(name string) string {
return "Hello " + name
}
// Called like this:
greeting := Hello("Dave")
// Multiple parameters, multiple return values
func SumAndMultiply(a, b int) (int, int) {
return a+b, a*b
}
// Called like this:
aplusb, atimesb := SumAndMultiply(a, b)
Assim como os parâmetros, os valores de retorno também podem ser nomeados, se você quiser.
Se forem usados valores de retorno nomeados, uma instrução return sem argumentos retorna esses valores.
Isso é conhecido como retorno nu.
func SumAndMultiplyThenMinus(a, b, c int) (sum, mult int) {
sum, mult = a+b, a*b
sum -= c
mult -= c
return
}
Também é importante esclarecer os conceitos de passagem por valor e passagem por referência.
Primeiro, vamos esclarecer a passagem por valor.
No exemplo abaixo, quando passamos a variável val para a função MultiplyByTwo, passamos uma cópia de val.
Por causa disso, newVal fica com o valor atualizado 4, mas a variável original val continua sendo 2.
Nos bastidores, o Go basicamente faz uma cópia do valor original, de modo que apenas essa cópia, ou seja, v, é modificada pela função.
val := 2
func MultiplyByTwo(v int) int {
v = v * 2
return v
}
newVal := MultiplyByTwo(val)
// newval is 4, val is still 2 because only a copy of its value was passed into the function
A rigor, todos os argumentos são passados por valor em Go, ou seja, é feita uma cópia do valor ou dos dados fornecidos à função.
Mas se você não quer fazer uma cópia dos dados passados a uma função e quer alterar esses dados dentro dela,
então deve usar pointers como argumentos, ou seja, passar por referência.
Usamos um pointer para conseguir a passagem por referência.
Ao passar argumentos do tipo pointer para uma função,
podemos modificar os dados subjacentes que foram passados para a função, em vez de operar apenas sobre uma cópia deles.
Por enquanto, basta saber que tipos de ponteiro podem ser reconhecidos pelo * antes do tipo na assinatura da função.
func HandlePointers(x, y *int) {
// Some logic to handle integer values referenced by pointers x and y
}
Se o conceito de pointer parece confuso, não se preocupe.
Temos uma seção dedicada mais adiante no programa de estudos para ajudar você a dominar ponteiros.
Repare que slices e maps são exceções à regra mencionada acima.
Quando passamos um slice ou um map como argumentos para uma função, eles são tratados como tipos de ponteiro, mesmo que não haja um * explícito no tipo.
Isso significa que, se passarmos um slice ou um map para uma função e modificarmos seus dados subjacentes,
as mudanças vão se refletir no slice ou no map original.
As funções em Go são consideradas cidadãs de primeira classe, o que as torna muito poderosas. Existem vários outros conceitos relacionados a funções, como métodos e funções anônimas, que você vai encontrar mais adiante na sua jornada.