Rust 0x03 = Funções;

Gil Mendes
6 min readJan 16, 2017

--

Se ainda não o fez, deverá ler o artigo anterior para ficar a perceber tudo sobre variáveis, tipos primitivos e toda a sua diversidade.

No artigo de hoje, vamos falar sobre funções. As funções são uma entidade bastante comum nas linguagens de programação. Estas podem opcionalmente aceitar parâmetros e/ou retornar um valor após a execução da sua lógica. Estas permitem criar um bloco de código que desempenha um determinado conjunto de operações, assim, somos capazes de separar e organizar o nosso código de forma facilitar a leitura e manutenção.

Todos os programas em Rust necessitam pelo menos de uma função, a função main:

fn main() {
}

A função acima é o mais simples possível. Como dito em artigos anteriores, fn quer dizer que “isto é uma função”, seguida pelo nome, e parênteses, uma vez que neste caso a função não aceita argumentos. Por fim temos um conjunto de chavetas que permitem abrir e fechar o corpo da função.

Para permitir a uma função receber argumentos apenas necessitamos de declarar o nome e o tipo, dentro dos parênteses. A função abaixo recebe um inteiro de 32-bit e imprime o seu valor, utilizando a macro println!:

fn print_number(x: i32) {
println!("x tem o valor de: {}", x);
}

Como podem ver, a declaração de argumentos funciona de forma muito semelhante as declarações usando o let. Ao contrario do let aqui é obrigatório fazer a declaração dos tipos dos argumentos.

Abaixo encontra-se um programa completamente funcional que faz a soma de dois números:

fn main() {
print_sum(10, 5);
}
fn print_sum(x: i32, y: i32) {
println!("{} + {} = {}", x, y, x + y);
}

Os argumentos são separados por vírgulas, tanto na chamada da função como na sua declaração.

Agora, vamos criar um função que retorne um valor para podermos analisar a sintaxe. A função a seguir calcula o dobro de um número passado por parâmetro e devolve o resultado desse cálculo.

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

As funções em Rust apenas retornam um valor, o tipo do valor devolvido deve ser declarado depois de uma “seta” (->).

A última linha da função é responsável por retornar um valor, para que isso aconteça não deve ser colocado o ponto e virgula (;) no final da linha. Caso contrario o compilador irá mostrar um erro:

error[E0269]: not all control paths return a value
--> src/main.rs:6:1
|
6 | fn double(x: i32) -> i32 {
| ^
|
help: consider removing this semicolon:
--> src/main.rs:7:10
|
7 | x * 2;
| ^
error: aborting due to previous error

Este pequeno pormenor, indica duas coisas sobre o Rust. O Rust é uma linguagem baseada em expressões e os ponto e virgula são diferentes de outras linguagens que usam chavetas e o ponto e virgula.

Palavra reservada “return”

O Rust também tem uma forma de fazer um retorno no meio da execução de uma função. Neste caso é necessário fazer uso da palavra reservada return, como pode ser visto a seguir:

fn example(x: i32) -> i32 {
return x;
// Este código nunca será executado!
x + 1
}

O return também pode ser usado na última linha da função, mas é considerado anti-padrão:

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

Esta prática, de não colocar a palavra return, pode parecer um pouco estranho no início, caso não esteja habituado a uma linguagens baseada em expressões.

Funções divergentes

O Rust contem uma sintaxe especial para as funções divergentes, ou seja, funções que não retornam:

fn divergente() -> ! {
panic!("Está função nunca retorna");
}

panic! é uma macro, semelhante ao println!. Ao contrário do println!, o panic! provoca uma excepção na thread atual o que a faz crashar e devolver uma mensagem. Como esta função irá crashar ele nunca irá retornar, assim sendo nós colocamos um ponto de exclamação (!), que é lido como “diverge”.

Agora, se criar um novo programa onde chama a nossa função divergente() no main, irá poder ver a seguinte mensagem na consola:

thread 'main' panicked at 'Está função nunca retorna', src/main.rs:6

Para vermos mais informação referente ao erro, é necessário ativar o backtrace. Para isso definimos a variável de ambiente RUST_BACKTRACE, como pode ver abaixo:

RUST_BACKTRACE=1 ./target/debug/diverges
thread 'main' panicked at 'Está função nunca retorna', src/main.rs:6
stack backtrace:
1: 0x10e6d861a - std::sys::imp::backtrace::tracing::imp::write::h917062bce4ff48c3
2: 0x10e6da27f - std::panicking::default_hook::{{closure}}::h0bacac31b5ed1870
3: 0x10e6d951f - std::panicking::default_hook::h5897799da33ece67
4: 0x10e6d9a96 - std::panicking::rust_panic_with_hook::h109e116a3a861224
5: 0x10e6d36b3 - std::panicking::begin_panic::h634e2b37a96f78d4
6: 0x10e6d3872 - hello_world::divergente::hfb2db8de03f2a159
7: 0x10e6d384a - hello_world::main::h27dfccf6bcca0919
8: 0x10e6da83a - __rust_maybe_catch_panic
9: 0x10e6d9d06 - std::rt::lang_start::hd661476ce2fc2931
10: 0x10e6d38a9 - main

Uma função divergente pode usar qualquer tipo:

fn main() {
let x: i32 = divergente();
let x: String = divergente();
}
fn divergente() -> ! {
panic!("Está função nunca retorna");
}

Apontadores para Funções

O Rust permite criar um variável que aponte para uma função, tal como pode ver abaixo:

let f: fn(i32) -> i32;

Neste caso, f é uma viável que aponta para uma função que recebe um i32 como argumento e retorna um i32. Abaixo é mostrado um exemplo de como fazer uso desta funcionalidade:

fn double(x: i32) -> i32 {
x * 2
}
fn main() {
// atribuição sem inferencia de tipos
let f: fn(i32) -> i32 = double;
// atribuição com inferencia de tipos
let outra_f = double;
// execução da função usando o apontador
f(2);
}

No que toca a funções, por hoje está terminado, vamos agora ver os padrões para os comentários.

Comentários

Agora que aprendemos a escrever uma função, é uma boa ideia aprender a comentar módulos, funções e linhas de código em Rust. Comentários são notas que se deixa no código para ajudar outros programadores a compreender o nosso código. O compilador ignora completamente estes comentários.

O Rust tem dois tipos de comentários, são eles, comentários de linha e comentários de documentação.

// Comentários de linha, é tudo após '//' e que se estende até ao final da linha.let xpto = 10; // isto é um comentário de linha// Se for uma explicação longa, você pode dividir os comentários em 
// múltiplas linhas. Coloque sempre um espaço após '//' para tornar
// os comentários mais legíveis.

O outro tipo de comentários, os de documentação, servem para isso mesmo, documentar o nosso código. Para os usar apenas é necessário usar três barras (///) em vez de apenas duas (//). Este tipo de comentários tem suporte à sintaxe Markdown.

/// Calcula o dobro do numero dado.
///
/// # Exemplo
///
/// ```
/// let five = 5;
///
/// assert_eq!(10, double(five));
/// ```
fn double(x: i32) -> i32 {
x * 2
}

Existe ainda outro tipo de comentários, //!. Este tipo é usado para documentar objetos que contêm items, como por exemplo: crates, módulos ou funções. É muito comum encontrar no ficheiro raiz de uma crate (lib.rs) ou na raiz de um módulo (mod.rs).

//! # The Rust Standard Library
//!
//! The Rust Standard Library provides the essential runtime
//! functionality for building portable Rust software.

É muito útil adicionar comentários ao nosso código, ainda mais se adicionar-mos alguns exemplos de utilização. Como já disse, o Rust foi pensado de raiz para fornecer todas as ferramentas que necessitamos para manter os nossos projetos. Por tanto, para gerar uma versão em HTML da documentação pode ter que usar o rustdoc. Mais à frente iremos falar deste utilitário em maior detalhe para obter o máximo partido do mesmo.

Por agora é tudo, fiquem em paz e boas codificações! 🙂

--

--