Funções são blocos de código reutilizáveis que realizam tarefas específicas. Em Rust, as funções são declaradas com a palavra-chave fn e seguem uma sintaxe clara e tipada. Nesta aula, exploraremos como declarar funções, passar parâmetros, retornar valores e entender a diferença fundamental entre statements e expressions, que influencia diretamente como escrevemos código funcional em Rust.

Rust é uma linguagem que valoriza a clareza e a segurança. As funções ajudam a organizar o código, evitar repetição e encapsular lógica. Além disso, Rust trata funções como cidadãs de primeira classe, permitindo passá-las como argumentos e retorná-las de outras funções, mas isso será abordado em aulas futuras. Por enquanto, focaremos nos fundamentos.

Declaração

Para declarar uma função em Rust, usamos a palavra-chave fn seguida do nome da função, parênteses para os parâmetros (que podem ser vazios) e um bloco de código entre chaves. O nome da função deve estar em snake_case (letras minúsculas e underscores).

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

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

Para chamar a função, basta usar seu nome seguido de parênteses:

fn main() {
    saudacao();
}

A ordem das funções não importa; você pode definir saudacao antes ou depois de main. Rust procura a função pelo nome em todo o escopo do módulo.

Parâmetros e tipos

Funções podem receber parâmetros, que são valores passados para a função. Cada parâmetro deve ter seu tipo explicitamente declarado. A sintaxe é: nome: Tipo. Múltiplos parâmetros são separados por vírgulas.

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

fn soma(a: i32, b: i32) {
    println!("A soma é {}", a + b);
}

Chamando a função:

fn main() {
    soma(5, 3);
}

Os parâmetros são vinculados aos argumentos passados. Rust é fortemente tipado, então os tipos devem corresponder. Você também pode passar referências ou outros tipos compostos, mas isso será visto em aulas posteriores.

Retorno e expressões

Funções podem retornar valores. O tipo de retorno é especificado após uma seta (->). Para retornar um valor, usamos a palavra-chave return ou, mais idiomático, a última expressão do bloco (sem ponto e vírgula).

Exemplo com return explícito:

fn dobro(x: i32) -> i32 {
    return x * 2;
}

Exemplo com retorno implícito (última expressão):

fn dobro(x: i32) -> i32 {
    x * 2
}

Note que não há ponto e vírgula no final de x * 2. Se colocássemos ponto e vírgula, a linha se tornaria um statement (que não retorna valor) e causaria um erro de tipo, pois a função espera um i32.

Funções que não retornam valor explicitamente têm tipo de retorno () (tupla vazia), que pode ser omitido.

Statements vs expressions

Em Rust, a distinção entre statements e expressions é crucial. Um statement é uma instrução que realiza uma ação, mas não produz um valor. Exemplos: declaração de variável com let, chamada de função com ponto e vírgula, etc. Uma expression é algo que avalia para um valor. Exemplos: 5 + 3, if condicao { valor } else { outro }, bloco de código.

A última expressão de um bloco (como o corpo de uma função ou um bloco {}) é retornada como valor do bloco. Statements não retornam valor, então não podem ser usados como última expressão se a função espera um retorno.

Exemplo de expressão com if:

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

Aqui, o if é uma expressão que retorna a ou b. Note que não há ponto e vírgula.

Já um let é um statement:

let x = 5; // statement, não retorna valor

Entender essa diferença ajuda a escrever código mais conciso e idiomático em Rust.

Boas práticas e observações

Prefira usar retorno implícito quando a função for pequena e clara. Use return para saídas antecipadas (ex: em validações). Sempre declare os tipos dos parâmetros e do retorno, exceto quando o retorno for (). Nomes de funções devem ser descritivos e em snake_case. Evite funções muito longas; quebre em funções menores.

Referências

Exercícios

  1. Crie uma função chamada quadrado que recebe um número inteiro e retorna seu quadrado. Use retorno implícito.

    ✓ Resposta:
    fn quadrado(x: i32) -> i32 {
        x * x
    }
  2. Escreva uma função saudacao_personalizada que recebe um nome (string slice &str) e imprime "Olá, [nome]!".

    ✓ Resposta:
    fn saudacao_personalizada(nome: &str) {
        println!("Olá, {}!", nome);
    }
  3. Implemente uma função eh_par que recebe um número e retorna true se for par, false caso contrário.

    ✓ Resposta:
    fn eh_par(n: i32) -> bool {
        n % 2 == 0
    }
  4. Crie uma função maximo que recebe dois números e retorna o maior. Use a expressão if.

    ✓ Resposta:
    fn maximo(a: i32, b: i32) -> i32 {
        if a > b { a } else { b }
    }
  5. Explique por que o código abaixo não compila e corrija-o: fn exemplo() -> i32 { let x = 5; x; }

    ✓ Resposta:O código não compila porque a última linha x; é um statement (devido ao ponto e vírgula) e não retorna valor. A função espera retornar i32. A correção é remover o ponto e vírgula: fn exemplo() -> i32 { let x = 5; x }. Assim, x é a última expressão e retorna o valor.