Funções são blocos fundamentais de código em Rust, permitindo organizar lógica, reutilizar código e abstrair comportamentos. Nesta aula, exploraremos como declarar funções, passar parâmetros com tipos explícitos, retornar valores e entender a diferença crucial entre statements e expressions, que influencia diretamente a sintaxe de retorno.

Em Rust, funções são definidas com a palavra-chave fn, seguida por um nome, parâmetros opcionais entre parênteses, e um corpo delimitado por chaves. A linguagem é fortemente tipada, então todos os parâmetros e o tipo de retorno devem ser declarados. Vamos mergulhar nos detalhes.

Declaração

A declaração de uma função em Rust começa com fn, seguido pelo nome da função (em snake_case por convenção), parênteses para parâmetros, e chaves para o corpo. Se a função não retorna valor, o tipo de retorno é omitido (equivalente a (), a tupla vazia).

Exemplo de uma função simples que imprime uma mensagem:

fn saudacao() {
    println!("Olá, mundo!");
}

Note que não há ponto e vírgula após a declaração; o corpo é um bloco de código. Funções podem ser chamadas de qualquer lugar dentro do escopo onde são visíveis.

Parâmetros e tipos

Parâmetros são declarados dentro dos parênteses, com nome seguido de dois pontos e o tipo. Múltiplos parâmetros são separados por vírgulas. Rust exige que todos os parâmetros tenham tipos explícitos, o que ajuda na segurança e clareza.

Exemplo de função que soma dois números:

fn soma(a: i32, b: i32) -> i32 {
    a + b
}

Os parâmetros a e b são do tipo i32 (inteiro de 32 bits). O tipo de retorno é especificado após uma seta ->. A expressão a + b é o valor retornado (sem ponto e vírgula).

É importante notar que Rust não suporta valores padrão para parâmetros nem sobrecarga de funções (diferentes assinaturas com o mesmo nome). Cada função tem um nome único.

Retorno e expressões

Em Rust, o valor de retorno de uma função é a última expressão do corpo, se não houver ponto e vírgula. Alternativamente, pode-se usar a palavra-chave return para retornar prematuramente. A expressão de retorno deve ser do tipo declarado.

Exemplo com return:

fn maior(a: i32, b: i32) -> i32 {
    if a > b {
        return a;
    }
    b
}

Aqui, se a > b, retornamos a imediatamente; caso contrário, a última expressão b é retornada. Note que b não tem ponto e vírgula, pois é uma expressão.

Funções também podem retornar múltiplos valores usando tuplas:

fn divisao_com_resto(a: i32, b: i32) -> (i32, i32) {
    (a / b, a % b)
}

Statements vs expressions

Em Rust, todo código é composto por statements (instruções) e expressions (expressões). Statements são comandos que realizam uma ação e não retornam valor (ex.: declaração de variável com let, chamada de função com ponto e vírgula). Expressions avaliam para um valor e podem ser compostas (ex.: 5 + 3, if blocks, blocos de código).

Um bloco {} é uma expression que retorna o valor da sua última expressão. Por exemplo:

fn main() {
    let y = {
        let x = 3;
        x + 1
    };
    println!("O valor de y é: {}", y); // imprime 4
}

Aqui, o bloco { let x = 3; x + 1 } é uma expression que retorna 4. Note que let x = 3; é um statement (termina com ponto e vírgula), enquanto x + 1 é uma expression (sem ponto e vírgula).

Entender essa diferença é crucial para escrever funções corretas: a última linha do corpo da função deve ser uma expression sem ponto e vírgula para que o valor seja retornado. Se você colocar um ponto e vírgula, a linha se torna um statement e a função retorna () (tupla vazia), a menos que explicitamente use return.

Boas práticas

  • Use nomes descritivos em snake_case para funções.
  • Prefira retornar valores implicitamente (sem return) para clareza, a menos que precise de retorno prematuro.
  • Evite funções muito longas; quebre em funções menores.
  • Documente funções públicas com /// para gerar documentação.

Funções são a base para módulos, traits e closures, que veremos em aulas futuras.

Referências

Exercícios

  1. Escreva uma função chamada quadrado que recebe um inteiro i32 e retorna o seu quadrado (também i32).

    ✓ Resposta:
    fn quadrado(x: i32) -> i32 {
        x * x
    }
  2. Crie uma função eh_par que recebe um i32 e retorna um booleano indicando se o número é par.

    ✓ Resposta:
    fn eh_par(n: i32) -> bool {
        n % 2 == 0
    }
  3. Implemente uma função fatorial que calcula o fatorial de um número inteiro não negativo (u32) usando recursão. Retorne u64 para evitar overflow.

    ✓ Resposta:
    fn fatorial(n: u32) -> u64 {
        if n == 0 {
            1
        } else {
            n as u64 * fatorial(n - 1)
        }
    }
  4. Escreva uma função soma_tupla que recebe uma tupla (i32, i32) e retorna a soma dos dois elementos como i32.

    ✓ Resposta:
    fn soma_tupla(t: (i32, i32)) -> i32 {
        t.0 + t.1
    }
  5. Crie uma função maior_de_tres que recebe três inteiros i32 e retorna o maior deles. Use a função max da biblioteca padrão se desejar, ou implemente manualmente.

    ✓ Resposta:
    fn maior_de_tres(a: i32, b: i32, c: i32) -> i32 {
        let maior_ab = if a > b { a } else { b };
        if maior_ab > c { maior_ab } else { c }
    }