Rust 0x04 = Controlo do Fluxo;

Gil Mendes
6 min readJan 18, 2017

--

Finalmente chegamos à parte de controlo do fluxo. Aqui, vamos falar de mecanismos que permitem criar múltiplos caminhos de execução, invés de apenas um único caminho linear, que usamos até agora.

Uma condição simples

Em Rust, um if não é particularmente complexo, é muito semelhante ao if que encontramos em linguagens dinamicamente tipadas.

Um if, permite criar diferentes caminhos na execução do nosso programa. É gerado de um ponto de decisão, que dependendo de uma escolha e múltiplos caminhos podem ser tomados.

O if apresentado abaixo desmembra-se em dois caminhos de execução:

if x == 5 {
println!("x é cinco!");
}

Se o valor de x for diferente de cinco, condição avaliada como false (falso), a mensagem não será impressa. Caso contrário, se a expressão depois do if for avaliada como true (verdadeira), então o bloco será executado e será apresentada a mensagem x é cinco.

No caso de querer que alguma ação aconteça quando a expressão é false, usa-se a palavra reservada else:

let x = 5;

if x == 5 {
println!("x é igual a 5");
} else {
println!("x não é igual a cinco! :(");
}

Se existir mais do que um caso para testar, usamos o else if:

let x = 6;

if x == 5 {
println!("x é igual a 5.");
} else if x == 6 {
println!("x é igual a 6.");
} else {
println!("x não é 5 nem 6!");
}

Mas os ifs podem ir muito além do que simples padrões, por exemplo, considere o código abaixo:

let x = 7;
let mut y = 0;

if x == 5 {
y = 10;
} else {
y = 20;
}

println!("y é igual a {}", y);

Este código pode ser optimizado e reescrito da seguinte forma:

let x = 7;

let y = if x == 5 { 10 } else { 20 };

println!("y é igual a {}", y);

Isto funciona porque o if é uma expressão. O valor da expressão é a última expressão do ramo que foi escolhido. Um if sem um else sempre retorna um tuplo vazio () como valor.

Loops

Atualmente, o Rust fornece três tipos de ciclos. São eles o loop, while e o for. Cada um deles tem o seu conjunto de casos de uso.

Loop

O loop permite a criação de um ciclo infinito. O programa apenas pode sair deste ciclo se for lançada uma excepção, o ciclo é quebrado ou um retorno é alcançado.

loop {
println!("Isto é um ciclo infinito!");
}

While

À semelhança de outras linguagens, o rust também tem disponível o ciclo while, que permite executar o ciclo até à condição de paragem ser verdadeira. Vejamos um exemplo deste ciclo:

let mut num = 0;

while num != 100 {
num += 10;
println!("{}", num);
}

O while é a escolha correta para quando não temos certeza de quantas vezes o ciclo irá acontecer.

No caso de ser necessário um ciclo infinito, tal como:

while true {

Não quer dizer que o while esteja errado, mas para estes casos temos o loop:

loop {

O Rust interpreta estes dois casos de forma diferentes, o loop tem muita mais performance, pois o compilador já sabe à partida que tem que iterar novamente, assim que atinge o fim do ciclo.

For

O ciclo for usa-se quando sabemos o número de vezes que o ciclo irá ser executado. Mas, não se deixe enganar pelo nome, o ciclo for funciona de uma forma um pouco diferente de outras linguagens. Por exemplo, considere o código C abaixo:

for (size_t num = 0; num < 10; num++) {
printf("> %d\n", num);
}

O equivalente em Rust é:

for num in 0..10 {
println!("> {}", num);
}

Ou seja, um for em Rust é equivalente a:

for var in expressão {
código
}

A expressão tem que ser um item capaz de ser convertido num iterador (iterador), usando o IntoIterator. O iterador devolve uma série de elementos, cada elemento é uma iteração para o ciclo. O valor da iteração é depois ligado à variável que existe apenas dentro do bloco do ciclo. Uma vez atingido o fim do corpo do ciclo, a próxima variável é “alimentada” e o ciclo é executado mais uma vez. Logo que acabem os elementos do ciclo, o for termina.

Neste exemplo em concreto, 0..10 é uma expressão que recebe uma posição de começo e de fim, esta devolve um iterador para cada um dos valores desse intervalo. O valor mais alto é exclusive, por isso o ciclo vai de 0 até 9 e não 10.

O Rust não possui um ciclo for à la C de propósito, pois controlar cada elemento de um ciclo é complicado e propenso a erros, isto também se aplica aos desenvolvedores de C mais experientes.

Enumerate

Quando é preciso manter o número de vezes que o ciclo já foi iterado ou qual o índice de determinado dado, deve-se usar a função .enumerate()

Num intervalo

for (index, value) in (5..11).enumerate() {
println!("índice = {} e valor = {}", index, value);
}

Resultado:

índice = 0 e valor = 5
índice = 1 e valor = 6
índice = 2 e valor = 7
índice = 3 e valor = 8
índice = 4 e valor = 9
índice = 5 e valor = 10

Não esqueça de adicionar parênteses no intervalo.

Em iteradores

let lines = "Um ficheiro\nMuito interessante".lines();

for (line, content) in lines.enumerate() {
println!("{}: {}", line, content);
}

E o resultado é:

0: Um ficheiro
1: Muito interessante

Terminar ciclos antes do final

No caso de ser necessário terminar um ciclo antes de terminar de percorrer todos os elementos, podemos fazê-lo com uma palavra reservada, mas antes vamos analisar o código abaixo.

let mut num = 5;
let mut done = false;

while !done {
num += num - 3;
println!("{}", num);

if num % 5 == 0 {
done = true;
}
}

Neste código foi necessário criar uma variável mutável, propositadamente, para ser usada como critério de paragem do ciclo. Neste caso a variável done necessita de ser true para o ciclo terminar. O Rust contém duas palavras reservadas para ajudar nesta situações, são elas o break e o continue.

Agora, já somos capazes de otimizar o nosso código anterior:

let mut num = 5;

loop {
num += num - 3;
println!("{}", num);

if num % 5 == 0 { break; }
}

Agora iteramos infinitamente e paramos, quebramos o ciclo com o break.

O continue é muito semelhante ao break, mas em vez de terminar o ciclo, passa para a próxima iteração. Por exemplo, abaixo são imprimidos todos os números pares entre 0 e 10:

for num in 0..11 {
if num % 2 != 0 { continue; }

println!("> {}", num);
}

Ciclos com etiqueta

Quando desenvolvemos aplicações mais complexas, podem aparecer pela frente situações onde seja necessário usar ciclos aninhados. Nessas situações, por vezes dava muito jeito conseguir especificar quais dos ciclos queremos quebrar. Para essas situações o Rust implementa um mecanismo que permite dar um nome (como uma label) aos ciclos, assim quando chamar-mos o break e/ou o continue podemos especificar a qual dos ciclos nos queremos referir.

'ciclo1: for x in 0..11 {
'ciclo2: for y in 0..11 {
if x % 2 == 0 { continue 'ciclo1; }
if y % 2 == 0 { continue 'ciclo2; }

println!("> ({}, {})", x, y);
}
}

O código acima imprime todas as coordenadas em que o valor de x e y são impares.

A vida é cheia de condições, assim como os nossos programas. Nada melhor do que ter ao nosso dispor um conjunto variado de formas de fazer iterações, sendo que cada uma delas tem utilizações bem específicas.

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

--

--