Criar funções

Concluído

No Go, as funções permitem agrupar um conjunto de instruções que você pode chamar em outras partes do aplicativo. Em vez de criar um programa com muitas instruções, use funções para organizar o código e torná-lo mais legível. Um código mais legível também oferece manutenção mais fácil.

Até este ponto, chamamos a função fmt.Println() e escrevemos um código na função main(). Nesta seção, exploraremos como criar funções personalizadas. Também examinaremos algumas outras técnicas que você pode usar com as funções no Go.

Função principal

A função com a qual você está interagindo é a função main(). Todos os programas executáveis no Go têm essa função porque ela é o ponto de partida do programa. Só é possível ter uma função main() no programa. Se você estiver criando um pacote Go, não precisará escrever uma função main(). Veremos como criar pacotes em um próximo módulo.

Antes de passarmos para os conceitos básicos da criação de funções personalizadas no Go, vamos examinar um aspecto crucial da função main(). Como você deve ter observado, a função main() não tem nenhum parâmetro e não retorna nada. Mas isso não significa que ela não pode ler valores do usuário, como argumentos da linha de comando. Caso precise acessar argumentos da linha de comando no Go, faça isso com o pacote do sistema operacional e a variável os.Args, que contém todos os argumentos transmitidos para o programa.

O seguinte código lê dois números da linha de comando e os soma:

package main

import (
    "fmt"
    "os"
    "strconv"
)

func main() {
    number1, _ := strconv.Atoi(os.Args[1])
    number2, _ := strconv.Atoi(os.Args[2])
    fmt.Println("Sum:", number1+number2)
}

A variável os.Args contém todos os argumentos da linha de comando transmitidos para o programa. Como esses valores são do tipo string, você precisará convertê-los em int para somá-los.

Para executar o programa, use este comando:

go run main.go 3 5

Esta é a saída:

Sum: 8

Vamos ver como podemos refatorar o código acima e criar nossa primeira função personalizada.

Funções personalizadas

Esta é a sintaxe usada para criar uma função:

func name(parameters) (results) {
    body-content
}

Observe que você usa a palavra-chave func para definir uma função e, depois, atribui um nome a ela. Depois do nome, especifique a lista de parâmetros para a função. Pode haver zero ou mais parâmetros. Redefina também os tipos de retorno da função, que também podem ser zero ou mais. (Falaremos sobre como retornar vários valores na próxima seção.) Depois de definir todos esses valores, você escreverá o conteúdo do corpo da função.

Para praticar essa técnica, vamos refatorar o código da seção anterior para somar os números em uma função personalizada. Usaremos este código:

package main

import (
    "fmt"
    "os"
    "strconv"
)

func main() {
    sum := sum(os.Args[1], os.Args[2])
    fmt.Println("Sum:", sum)
}

func sum(number1 string, number2 string) int {
    int1, _ := strconv.Atoi(number1)
    int2, _ := strconv.Atoi(number2)
    return int1 + int2
}

Esse código cria uma função chamada sum que usa dois argumentos string, converte-os em int e retorna o resultado da soma deles. Quando você define um tipo de retorno, a função precisa retornar um valor desse tipo.

No Go, você também pode definir um nome para o valor retornado de uma função, como se fosse uma variável. Por exemplo, você poderá refatorar a função sum desta forma:

func sum(number1 string, number2 string) (result int) {
    int1, _ := strconv.Atoi(number1)
    int2, _ := strconv.Atoi(number2)
    result = int1 + int2
    return
}

Observe que agora você precisará colocar o valor do resultado da função entre parênteses. Use também a variável dentro da função e apenas adicione uma linha return no final. O Go retornará os valores atuais dessas variáveis de retorno. A simplicidade de escrever a palavra-chave return no final da função é interessante (especialmente quando há mais de um valor retornado). Não recomendamos essa abordagem. Pode não ficar claro o que a função está retornando.

Retornar vários valores

No Go, uma função pode retornar mais de um valor. Você define esses valores de uma forma semelhante a como você define os parâmetros da função. Em outras palavras, você especifica um tipo e um nome, mas o nome é opcional.

Por exemplo, digamos que você deseje criar uma função que soma dois números, mas também os multiplica. O código da função ficará assim:

func calc(number1 string, number2 string) (sum int, mul int) {
    int1, _ := strconv.Atoi(number1)
    int2, _ := strconv.Atoi(number2)
    sum = int1 + int2
    mul = int1 * int2
    return
}

Agora você precisará de duas variáveis para armazenar os resultados da função. (Caso contrário, ela não será compilada). Esta é a aparência dele:

package main

import (
    "fmt"
    "os"
    "strconv"
)

func main() {
    sum, mul := calc(os.Args[1], os.Args[2])
    fmt.Println("Sum:", sum)
    fmt.Println("Mul:", mul)
}

Outro recurso interessante do Go é que, se você não precisar de um dos valores retornados de uma função, poderá descartá-lo atribuindo o valor retornado à variável _. A variável _ é a maneira idiomática do Go ignorar os valores retornados. Ela permite que o programa seja compilado. Portanto, caso deseje obter apenas o valor da soma, use este código:

package main

import (
    "fmt"
    "os"
    "strconv"
)

func main() {
    sum, _ := calc(os.Args[1], os.Args[2])
    fmt.Println("Sum:", sum)
}

Veremos mais sobre como ignorar os valores retornados das funções quando explorarmos o tratamento de erro em um próximo módulo.

Alterar os valores de parâmetro de uma função (ponteiros)

Quando você transmite um valor para uma função, as alterações feitas nessa função não afetam o chamador. O Go é uma linguagem de programação de "passagem por valor". Sempre que você passar um valor para uma função, o Go usará esse valor para criar uma cópia local (uma nova variável na memória). As alterações feitas nessa variável na função não afetam aquela que você enviou para a função.

Por exemplo, digamos que você crie uma função para atualizar o nome de uma pessoa. Observe o que acontece quando você executa este código:

package main

import "fmt"

func main() {
    firstName := "John"
    updateName(firstName)
    fmt.Println(firstName)
}

func updateName(name string) {
    name = "David"
}

Embora você tenha alterado o nome para "David" na função, a saída ainda é "John". A saída não foi alterada porque a alteração na função updateName modifica apenas a cópia local. O Go transmitiu o valor da variável, não a variável em si.

Se desejar que a alteração feita na função updateName afete a variável firstName na função main, você precisará usar um ponteiro. Um ponteiro é uma variável que contém o endereço de memória de outra variável. Quando você envia um ponteiro para uma função, não está passando um valor, está um endereço de memória. Portanto, cada alteração feita nessa variável afeta o chamador.

No Go, há dois operadores usados para trabalhar com ponteiros:

  • O operador & usa o endereço do objeto que o segue.
  • O operador * desreferencia um ponteiro. Ele fornece acesso ao objeto no endereço contido no ponteiro.

Vamos modificar nosso exemplo anterior para esclarecer como os ponteiros funcionam:

package main

import "fmt"

func main() {
    firstName := "John"
    updateName(&firstName)
    fmt.Println(firstName)
}

func updateName(name *string) {
    *name = "David"
}

Execute o código anterior. Observe que a saída agora mostra David em vez de John.

A primeira coisa que você precisará fazer é modificar a assinatura da função para indicar que deseja receber um ponteiro. Para fazer isso, altere o tipo de parâmetro de string para *string. (O último ainda é uma cadeia de caracteres, mas agora é um ponteiro para uma cadeia de caracteres.) Em seguida, ao atribuir um novo valor a essa variável, você precisará adicionar a estrela (*) no lado esquerdo da variável para gerar o valor dessa variável. Ao chamar a função updateName, você não envia o valor, mas o endereço de memória da variável. O símbolo & no lado esquerdo da variável indica o endereço da variável.