# Control structures ## Conditionals We've seen `if` pass by a few times already. To recap, braces are compulsory, an optional `else` clause can be appended, and multiple `if`/`else` constructs can be chained together: if false { std::io::println("that's odd"); } else if true { std::io::println("right"); } else { std::io::println("neither true nor false"); } The condition given to an `if` construct *must* be of type boolean (no implicit conversion happens). If the arms return a value, this value must be of the same type for every arm in which control reaches the end of the block: fn signum(x: int) -> int { if x < 0 { -1 } else if x > 0 { 1 } else { ret 0; } } The `ret` (return) and its semicolon could have been left out without changing the meaning of this function, but it illustrates that you will not get a type error in this case, although the last arm doesn't have type `int`, because control doesn't reach the end of that arm (`ret` is jumping out of the function). ## Pattern matching Rust's `alt` construct is a generalized, cleaned-up version of C's `switch` construct. You provide it with a value and a number of arms, each labelled with a pattern, and it will execute the arm that matches the value. # let my_number = 1; alt my_number { 0 { std::io::println("zero"); } 1 | 2 { std::io::println("one or two"); } 3 to 10 { std::io::println("three to ten"); } _ { std::io::println("something else"); } } There is no 'falling through' between arms, as in C—only one arm is executed, and it doesn't have to explicitly `break` out of the construct when it is finished. The part to the left of each arm is called the pattern. Literals are valid patterns, and will match only their own value. The pipe operator (`|`) can be used to assign multiple patterns to a single arm. Ranges of numeric literal patterns can be expressed with `to`. The underscore (`_`) is a wildcard pattern that matches everything. If the arm with the wildcard pattern was left off in the above example, running it on a number greater than ten (or negative) would cause a run-time failure. When no arm matches, `alt` constructs do not silently fall through—they blow up instead. A powerful application of pattern matching is *destructuring*, where you use the matching to get at the contents of data types. Remember that `(float, float)` is a tuple of two floats: fn angle(vec: (float, float)) -> float { alt vec { (0f, y) if y < 0f { 1.5 * float::consts::pi } (0f, y) { 0.5 * float::consts::pi } (x, y) { float::atan(y / x) } } } A variable name in a pattern matches everything, *and* binds that name to the value of the matched thing inside of the arm block. Thus, `(0f, y)` matches any tuple whose first element is zero, and binds `y` to the second element. `(x, y)` matches any tuple, and binds both elements to a variable. Any `alt` arm can have a guard clause (written `if EXPR`), which is an expression of type `bool` that determines, after the pattern is found to match, whether the arm is taken or not. The variables bound by the pattern are available in this guard expression. ## Destructuring let To a limited extent, it is possible to use destructuring patterns when declaring a variable with `let`. For example, you can say this to extract the fields from a tuple: # fn get_tuple_of_two_ints() -> (int, int) { (1, 1) } let (a, b) = get_tuple_of_two_ints(); This will introduce two new variables, `a` and `b`, bound to the content of the tuple. You may only use irrevocable patterns—patterns that can never fail to match—in let bindings, though. Things like literals, which only match a specific value, are not allowed. ## Loops `while` produces a loop that runs as long as its given condition (which must have type `bool`) evaluates to true. Inside a loop, the keyword `break` can be used to abort the loop, and `cont` can be used to abort the current iteration and continue with the next. let x = 5; while true { x += x - 3; if x % 5 == 0 { break; } std::io::println(int::str(x)); } This code prints out a weird sequence of numbers and stops as soon as it finds one that can be divided by five. There's also `while`'s ugly cousin, `do`/`while`, which does not check its condition on the first iteration, using traditional syntax: # fn eat_cake() {} # fn any_cake_left() -> bool { false } do { eat_cake(); } while any_cake_left(); When iterating over a vector, use `for` instead. for elt in ["red", "green", "blue"] { std::io::println(elt); } This will go over each element in the given vector (a three-element vector of strings, in this case), and repeatedly execute the body with `elt` bound to the current element. You may add an optional type declaration (`elt: str`) for the iteration variable if you want. For more involved iteration, such as going over the elements of a hash table, Rust uses higher-order functions. We'll come back to those in a moment. ## Failure The `fail` keyword causes the current [task][tasks] to fail. You use it to indicate unexpected failure, much like you'd use `exit(1)` in a C program, except that in Rust, it is possible for other tasks to handle the failure, allowing the program to continue running. `fail` takes an optional argument, which must have type `str`. Trying to access a vector out of bounds, or running a pattern match with no matching clauses, both result in the equivalent of a `fail`. [tasks]: task.html ## Logging Rust has a built-in logging mechanism, using the `log` statement. Logging is polymorphic—any type of value can be logged, and the runtime will do its best to output a textual representation of the value. log(warn, "hi"); log(error, (1, [2.5, -1.8])); The first argument is the log level (levels `info`, `warn`, and `error` are predefined), and the second is the value to log. By default, you *will not* see the output of that first log statement, which has `warn` level. The environment variable `RUST_LOG` controls which log level is used. It can contain a comma-separated list of paths for modules that should be logged. For example, running `rustc` with `RUST_LOG=rustc::front::attr` will turn on logging in its attribute parser. If you compile a program named `foo.rs`, its top-level module will be called `foo`, and you can set `RUST_LOG` to `foo` to enable `warn` and `info` logging for the module. Turned-off `log` statements impose minimal overhead on the code that contains them, so except in code that needs to be really, really fast, you should feel free to scatter around debug logging statements, and leave them in. Three macros that combine text-formatting (as with `#fmt`) and logging are available. These take a string and any number of format arguments, and will log the formatted string: # fn get_error_string() -> str { "boo" } #warn("only %d seconds remaining", 10); #error("fatal: %s", get_error_string()); ## Assertions The keyword `assert`, followed by an expression with boolean type, will check that the given expression results in `true`, and cause a failure otherwise. It is typically used to double-check things that *should* hold at a certain point in a program. let x = 100; while (x > 10) { x -= 10; } assert x == 10;