Sage-Code Laboratory
index<--

Control Flow

Control flow statements make a language Turing Complete. Control statements are used to alter the linear logic workflow of computations. A program can have logic decisions, branches, selectors and loops. Rust is implementing all these features like a classic structure programming language.

Page Bookmarks


Conditional

Conditional statement is based on {if, else} keywords. You can create two program branches. When a condition is true then first branch is executed. When the condition is false the other branch is executed. The else branch is optional but the first is mandatory.

decision

Conditional Execution


Example:

fn main() {
    let a = 10;
    let b = 10;

    if a == b {
        println!("a and b are equal"); //expected
    }
    else
    {
        println!("a and b are not equal"); //unexpected
    }
}

Notes:

if expression

In many languages the decision is only a statement. In Rust the if is actually an expression that can have a result. The result is given by the value of the last expression in the block. This is specific to Rust and looks a little bit strange for programmer. Sometimes may be useful to think like this especially when there is no ternary operator like "?" used in other languages.

Example:

/* demo for conditional expression */
fn main() {
    let condition: bool = false; //boolean
    let number = if condition { 5 } else { 6 };
    println!("The value of number is: {}", number);
}

Note:

Ladder Selector

In Python we use elsif keyword and in Julia we use elseif keyword to create a multi-deck decision. Here in Rust we do not have a such keywords. Instead you can use else if that are two keywords. This is possible in Rust due to lack of any symbol required after the "else". In Python after else you must use ":" but in Rust you can use a block { ... } or another if statement to for a multi-path decision block.

decision ladder

Decision Ladder


Example:

fn main() {
    use std::io;
    /* read a number */
    println!("enter a number:");
    let mut input = String::new();
    io::stdin().read_line(&mut input).unwrap();
    let number: i32 = input.trim().parse().unwrap();

    /* check number */
    if number % 2 == 0 {
            println!("this number is divisible by 2");
        } else if number % 3 == 0 {
            println!("this number is divisible by 3");
        } else if number % 5 == 0 {
            println!("this number is divisible by 5");
        } else if number % 7 == 0 {
            println!("this number is divisible by 7");
        } else {
            println!("this number is not divisible by 2,3,5 or 7");
    }
    println!("done");
}

Homework:Open this example and run it: ladder

Match Selector

There is one more control statement that is very important in Rust. This will replace "switch" or "case" that is available in Julia and Level. The idea of this statement is simple. We create a structure to check several conditions. We execute one statement for witch the condition is true. The beauty of Rust is that this condition is verified such way that all cases are covered. If we do not cover all cases a compiler error will be generated. "match" is one of the most powerful things in Rust.

Example:

/* define enumeration */
enum Coin {
    Nickel,
    Dime,
    Quarter,
}

/* use "match" statement */
fn penny(coin: Coin) -> u32 {
    match coin {
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter => 25,
    }
}

/* test penny() function */
fn main() {
   use Coin::{Quarter, Dime, Nickel};
   println!("A quarter has {} penny",penny(Quarter));
   println!("A dime has {} penny",penny(Dime));
   println!("A Nickel has {} penny",penny(Nickel));
}

Notes:

  1. The match has a special syntax symbol: "=>" this is like a "result" and is not used in any other place in the language. The function result type has a similar symbol "->". I have no idea why the symbol is different. It could be the same in my opinion.
  2. The symbol "::" is used here to extract element members from Coin. In my opinion we could use ".". For some reason I do not like this symbol "::". In Level language we use "." for all membership operations.
  3. The last element into enumeration has a coma after it. "Quarter,". In my opinion this is not necessary. I have look twice in the manual and this is the syntax. I can't complain but looks bad.

Homework: This example is on repl.it. Open and run it: penny

Repetition

A repetition statement will cause a block of code to repeat several times. The repetitive statement can be a block of code or an expression. The number of times it will be repeated can be controlled by a condition. If the condition is never true the repetition can be infinite and that is a bad situation that must be avoided.

Infinite Loop

This is the most simple repetition statement. We use only keyword "loop" and { ... } to create a repetitive block. This will be repeated forever and the program will get stack in the loop. You can stop using CTRL+C. This is actually a logical error. Good programmers must avoid infinite loops at all cost.

loop

Loop Diagram


Example:

fn main() {
        loop {
            println!("press ctrl+c to stop me!");
        }
    }
    

Note: This example is not available to be run, it is rude to block the session.

Intreruptions

You can intrerupt a loop by using breack and continue statements. These two statements are optional. If a repetitive statement has too many intreruptions is hard to read, so avoid using these statements as much as possible.

Break

To avoid an infinite loop you must create a break using a conditional stop.In the next example we create a control variable "i" that is declared outside of the loop and start with 1. Then you can increment this variable with +1 every iteration. You can use the relation operator "i > 3". This loop will execute 3 times then will stop and program will terminate.

loop break

Loop Break


Example:

fn main() {
        let mut i = 1;
        loop {
            println!("Variable i is now {}!",i);
            if i > 3 {
               break;
            } else {
               i += 1;
            } //end if
        } // end loop
    } // end main
    
Note: In this example you can see comments ending the blocks? This is a good practice rule but is not mandatory. You can add comments for end of blocks whenever you have nested blocks to end the curly brackets nightmare.

Continue

You can use continue keyword to start over. This will create a kind of shortcut that will skip all other statements and restart the loop. You can continue a loop from inside a nested block.

loop continue

Loop Continue


Example:

fn main() {
        let mut i = 1;
        loop {
            if i < 3 {
               i += 1;
               println!("Variable i is now {}!",i);
               continue;
            } break;
        } // end loop
    } // end main
    

While loop

This is a repetitive block of code that is executed as long as one condition is true. When the condition become false the program continue with next statement after the loop block end. Now the problem is to create a condition expression that will become false. If the condition never become false we again can have an infinite loop.

while loop

While Loop


Example:

fn main() {
        let mut number = 3;
    
        while number != 0 {
            println!("{}!", number);
    
            number = number - 1;
        } // end while
    
        println!("LIFTOFF!!!");
    } // end main
    

Range loop

This kind of loop is created using keyword "for" and it has two usages: One is to iterate a specific number of times over a range of numbers. Second is to iterate over a collection of items: array, vector, string or map/dictionary. The range in Rust is created using a range expression with .. operator. (1..10) will create one number and will assign it to "i" to create the control variable.

for loop

For Loop
with continue & break


Syntax

// display numbers 0 to 9;
    for i in range {
        //first block
        if condition {
           continue;
        }
        //second block
        if condition {
           break;
        }
        //third block
    }//end for
    

Example 1: Range Loop

Next program demonstrate range 0..10, we observe last number is 9 and not 10. The range do not include the outer limit. This is how ranges work in Rust. This loop do not have any alterations (break/continue) are not used.

// display numbers 0 to 9;
    fn main() {
        // the lust number i will not be generated that is the Range definition
        for i in 0..10 {
            println!("{}!", i);
        }//end for
        println!("done!");
    } //end main
    

Example 2: Nested Loops

Next program demonstrate nested loops. We generate prime numbers. The inner loop has an alteration. We break the loop if the number is not a prime number.

// generate prime numbers < 30
    fn main() {
        for i in 1..30 {
            for j in 2..i {
              if (i % j) == 0 {
                break;
              };
              if j == (i-1) {
                 print!("{},", i);
              };
            };
        }//end for
        println!("done!");
    } //end main
    

Homework: This example is available on repl.it for testing. open on repl.it and run it. Then modify the program to generate prime numbers < 1000.

For each

This is the most useful loop that is actually the iterative loop. It will visit each item from a collection until the last element is visited. The peculiar thing specific to Rust is that we have to specify method "iter()" to get an iterator that is actually a function that can be used to fetch the next element.

fn main() {
        let a = [10, 20, 30, 40, 50];
        // next we iterate over all members of array "a"
        for element in a.iter() {
            println!("the value is: {}", element);
        } // end for
    } // end main
    

Read next: Functions