rust/doc/tutorial/control.md

189 lines
6.7 KiB
Markdown
Raw Normal View History

# 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.
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) when y < 0f { 1.5 * std::math::pi }
(0f, y) { 0.5 * std::math::pi }
(x, y) { std::math::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 `when 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:
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(std::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:
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`.
2011-11-01 15:41:14 +01:00
[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 "hi";
log (1, [2.5, -1.8]);
By default, you *will not* see the output of your log statements. The
environment variable `RUST_LOG` controls which log statements actually
get output. 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 `foo.rs`, you can set `RUST_LOG` to
`foo` to enable its logging.
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.
For interactive debugging, you often want unconditional logging. For
this, use `log_err` instead of `log` [FIXME better name].
## 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;