Control Flow

Being able to create and manipulate variables is useful, but without the ability to run control flow such as conditions, loops, etc. a program is fairly limited in what it can do.

If Statements

If statements are the foundation of programming. If it is raining, you bring an umbrella. If you are hungry, you get something to eat. By using conditions in your code, you can have greater control on what your program can do:

pub fn printItem(int a)
{
    if (a == 0)
        println("0");
    else if (a == 1 || a == 2) {
        println("1");
        println("2");
    } else
        println("Other");
        println("Hi!");
}

Output (a = 0):

0
Hi!

Output (a = 1):

1
Hi!

Output (a = 2):

2
Hi!

Output (a = 3):

Other
Hi!

As shown above, the output depends on the input value of a. Note that the brackets {} are necessary for executing multiple statements. Indentation does not matter, and the statement directly after the if is “inside’ the if. Since the last println statement is after the statement after the if and is not in brackets, it is outside. Thus, it gets called no matter what the input of a is.

Match

The match statement is useful for situations in which you have a lot of various possible values for a variable and have different code actions for each. For example:

fn magicNumberGuess(int num) -> bool
{
    match (num) {
        3:
        7:
            println("I like this number!");
        2:
            println("It's a very basic number.");
        10..=100:
            println("Bigger than 9 and less than 101.");
        3:
        6:
            println("I really do not like this number.");
            return false;
        _:
            println("I have no opinions on this number.");
    }
    return true;
}

You may notice that unlike the traditional switch statements in C, there is no fall-through with labels, though you are allowed to have multiple conditions for each result (3 and 7 both lead to I like this number! being shown), as well as have multiple statements done as result as the condition. The _ case will catch anything. Note that the first condition hit is matched, so I really do not like this number. will never be shown for 3 and the _ case should always be done last. Note that this can work for anything, not just numbers:

fn matchStr(str s) -> int
{
    match (s) {
        "Red":
            return 0;
        "Green":
            return 1;
        "Orange":
        "Purple":
            return 2;
        _:
            return -1;
    }
}

Also note that it is not required to match every possible value in a match statement. We could decide to not match the “default” case with _:

fn matchItem(int num)
{
    match (num) {
        3:
            println("You win!");
    }
}

Loops

Sometimes you want to run code multiple times. Sometimes that amount of times depends on an input given. For example, you should wash as many hands as you have. While it may be tempting to hardcode this value to 2, some other unknown intelligent lifeform may have 4. First, we must go over the loop statement:

fn main()
{
    loop println("Hello World!");
}

Output:

Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
[Continues Infinitely]

To stop this program, press Ctrl+C on your terminal. While looping like this is a cool thing that we are able to do, it does not seem particularly useful if we can not exit the loop. Luckily, break exists for this purpose:

fn main()
{
    loop {
        println("Hello World!");
        break;
    }
}

Output:

Hello World!

Remember that we need to put the statements inside a {}, otherwise only the first statement will be part of the loop. The break statement allows us to break out of a loop we are in. The above break; is actually shorthand for break 1; This does imply you can break out of multiple loops:

fn main()
{
    loop {
        loop {
            loop {
                break 2;
                println("0");
            }
            println("1");
        }
        println("2");
        break 1;
    }
    println("3");
}

Output:

2
3

The break 2; breaks out of the first 2 loops, which leaves us to the println(2); statement. Then we break out of the final loop. While this breaking structure is neat, it does not allow us to do much, or does it?

fn count(int endNum)
{
    int num = 1;
    loop {
        if (num > endNum) break;
        println(num++);
    }
}

fn main()
{
    count(5);
}

Output:

1
2
3
4
5

Now we’re getting somewhere! First, we set num to 1. Inside the loop, if num is greater than the value of endNum we exit the loop. Otherwise, it first prints the value of num, increments the value of num (adds 1 to it), then jumps to the top of the loop repeating the condition check and print call until it exits.

While Loops

Surely there must be an easier way than the above code? Luckily, while loops automatically check a condition at the top of the loop and break if it is not met. The below code accomplishes the same task as the code from earlier:

fn count(int endNum)
{
    int num = 1;
    while (num <= endNum)
        println(num++);
}

fn main()
{
    count(5);
}

Output:

1
2
3
4
5

It’s important to note that the condition runs at the top of the loop, not for every statement in the loop:

fn count(int endNum)
{
    int num = 1;
    while (num <= endNum) {
        println(num++);
        println(num++);
    }
}

fn main() {
    count(5);
}

Output:

1
2
3
4
5
6

Since the value of num is only checked at the top of the loop, even though num becomes 6 during the course of the loop, it is still printed anyways.

Do While Loops

What if you wanted to run a condition at the end of a loop rather than the beginning? For this purpose, you can use a do while loop:

fn count(int endNum)
{
    int num = 1;
    do {
        println(num++);
    } while (num <= 5);
}

fn main()
{
    count(5);
    count(0);
}

Output:

1
2
3
4
5
6
1

Note that a do while loop guarantees the code in the loop will be executed at least once.

For Loops

It’s annoying that we have to increment num ourselves and declare it outside the loop. With for loops, this problem is taken care for us:

fn count(int endNum)
{
    for (int i = 1; i <= endNum; i++)
        println(i);
}

fn main()
{
    count(5);
}

Output:

1
2
3
4
5

The first part of the for loop has an expression that is done once before the loop. The next expression is the condition to check before each loop. The loop only continues if this condition succeeds. Finally, there is an expression to run at the end of the loop. Here, the variable i is incremented. It is common practice for for loop iterators to be i, j, or k.

Iterating

You may also use a for loop for iterating over an iterable list. You may iterate over anything that implements Iterable:

fn printListItems(List<int> items)
{
    for (int item : items)
        println(item);
}

The above code will print all of the items in the given list. You can also iterate using the .. (Range) or ..= (RangeEq) operator:

fn main()
{
    for (int i : 1..5)
        println(i);
    for (int i : 1..=5)
        println(i);
}

Output:

1
2
3
4
1
2
3
4
5
Iterating With Index

You are also allowed to iterate with an index by combining the element type with an int type in the tuple:

fn printListItems(List<int> items)
{
    for ((int: item, int: index) : items)
        println("Item at index {index} is {item}.");
}

Remember that named tuples exist and is what we are using here. The tuple does not necessarily have to be named, but it makes code quicker to read and write. While not necessary, surrounding your tuple with parenthesis makes it easier to read. Any modifications to index will change the iteration to be at the new index. For example, setting index to always be 0 in the for loop will cause it to never end.

Do For Loops

Like the do while loops, do for loops run at least once:

fn count(int endNum) {
    do (int i = 0)
        println(i);
    for (i <= endNum; i++)
}

fn main() {
    count(5);
    count(0);
}

Output:

1
2
3
4
5
6
1

It’s important to note that the condition is checked before the final expression is ran. In the example above, since i is 5 it passes the condition of being less than or equal to endNum (endNum is 5). This means the loop goes for another iteration, allowing 6 to be printed. Note that you may not loop over iterators as done in for loops.

Continue

Continues allow you to jump to the end of a loop:

fn main()
{
    for (int a = 0; a < 6; a++) {
        if (a == 4)
            continue;
        println(a);
    }
}

Output:

0
1
2
3
5

When a is 4, we jump to the end of the for loop. At this point we increment a, then jump to the top of the loop where we check the condition a < 6. You are also allowed to continue out of multiple loops like with break:

fn main()
{
    for (int a = 0; a < 2; a++) {
        for (int b = 0; b < 3; b++) {
            if (b == 2)
                continue 2;
            println(a + ", " + b);
        }
    }
}

Output:

0, 0
0, 1
1, 0
1, 1

Recursion Recursion Recursion

One method of control flow that uses function calls rather than loops is called recursion, in which a function calls itself. For example:

fn fib(int n)
{
    if (n <= 0)
        return 0;
    else if (n == 1)
        return 1;
    else
        return fib(n - 1) + fib(n - 2);
}

The above calculates the Fibonacci number for number n. Note that for large values of n, this will grow the call stack a large amount. In order to avoid this, tail recursion can be used. Tail recursion is when the last thing done by the function is calling itself. This means we can replace the call stack with the new one, therefore avoiding growing the stack:

fn fib(int n, int prev = 0, int curr = 1)
{
    if (n == 0)
        return prev;
    else if (n == 1)
        return curr;
    else
        return fib(n - 1, curr, prev + curr);
}

In the above example, either a value is returned or the current call stack is replaced with a new one. This is different from the first recursive example, as we don’t need to wait for the result of the function call to do calculations. The result is the new function call.