Skip to content

Control flow

Candela has if, three loops, and match. Every body is a brace-delimited block, and braces are never optional.

if / else

fn main() {
    let temperature = 30;
    if temperature > 25 {
        print("warm");
    } else if temperature > 10 {
        print("mild");
    } else {
        print("cold");
    }
}

The condition is not parenthesised. It is a bool expression: candela has no truthiness, so compare explicitly rather than testing a number or a string. A condition of another type is a compile error naming the type it has, and a condition typed any is checked when it runs and raises bad_downcast on anything but a bool; see errors. The same holds for else if, for while, and for the expression form below.

A condition that tests a value with is gives its body more than a bool: a tested variable has the type it was tested for, and a variant test such as opt is Some(x) binds the payload. See testing a type.

if as an expression

An if written where a value is expected produces a value. Each branch is a single expression with no semicolon, and an else branch is required so that every path yields something. A match works the same way.

fn main() {
    let size = if 3 > 2 { "big" } else { "small" };
    print(size);
}

while

while repeats a block for as long as its condition holds. The condition is a bool, exactly as in an if, and a value of another type is rejected the same way it is there.

fn main() {
    let countdown = 3;
    while countdown > 0 {
        print(countdown);
        countdown -= 1;
    }
}

for

for walks a range or a collection.

A range is written start..end and covers start up to but not including end. Leaving the start out begins at zero.

fn main() {
    for i in 0..3 {
        print(i);
    }
    for i in ..2 {
        print(i);
    }
}

Iterating a list binds each element in turn; iterating a map binds each key. Iterating a string binds each character as a one-character string.

fn main() {
    for word in ["a", "b"] {
        print(word);
    }

    let counts = {"x": 1, "y": 2};
    for key in counts {
        print(key, counts[key]);
    }
}

The loop variable belongs to the loop and is not in scope after it.

loop

loop repeats until something breaks out of it.

fn main() {
    let n = 0;
    loop {
        n += 1;
        if n == 3 {
            break;
        }
        print(n);
    }
}

break and continue

break leaves the innermost loop; continue skips to its next iteration. Both work in for, while, and loop.

fn main() {
    for i in 0..5 {
        if i == 1 {
            continue;
        }
        if i == 3 {
            break;
        }
        print(i);
    }
}

match

match compares a value against a list of arms and runs the first that fits. An arm is pattern => { ... } with a block, or pattern => expression, with one expression and a comma before the next arm; the comma after the last arm is optional. _ is the catch-all. A match needs at least one arm that is not the wildcard, and the wildcard comes last.

fn main() {
    let code = 2;
    match code {
        1 => print("one"),
        2 => {
            print("two");
            print("and more");
        }
        _ => print("something else"),
    }
}

Arms match by equality, so any type you can compare works, strings included. | between patterns makes one arm for several values.

fn main() {
    match "b" {
        "a" | "b" => print("early"),
        "c" => print("third"),
        _ => print("other"),
    }
}

Matching an enum matches on the variant instead, and binds the payload; see Enums.

match as an expression

A match written where a value is expected produces a value: the value of the arm that fits. Like an if used as a value, it has to give one on every path, so it needs a _ arm unless it matches an enum and lists every variant. A braced arm holds one expression, as the branches of an if used as a value do.

fn main() {
    let n = 3;
    let size = match n {
        0 => "none",
        1 | 2 => "few",
        _ => "many",
    };
    print(size);
}

Blocks and scope

A bare { ... } is a block. It groups statements and scopes the variables declared inside it, which is occasionally useful for keeping a temporary out of the surrounding function.

fn main() {
    let total = 0;
    {
        let temp = 5;
        total += temp;
    }
    print(total);
}

Loop bodies, if branches, and match arms are blocks and scope the same way. See Variables for the scoping rules.

A block holds statements. A fn declaration written inside one is a compile error, since functions belong at the top level of the file. See Functions.