2015-04-07 21:16:02 -05:00
|
|
|
% `if`
|
|
|
|
|
|
|
|
Rust's take on `if` is not particularly complex, but it's much more like the
|
|
|
|
`if` you'll find in a dynamically typed language than in a more traditional
|
|
|
|
systems language. So let's talk about it, to make sure you grasp the nuances.
|
|
|
|
|
|
|
|
`if` is a specific form of a more general concept, the *branch*. The name comes
|
|
|
|
from a branch in a tree: a decision point, where depending on a choice,
|
|
|
|
multiple paths can be taken.
|
|
|
|
|
|
|
|
In the case of `if`, there is one choice that leads down two paths:
|
|
|
|
|
|
|
|
```rust
|
|
|
|
let x = 5;
|
|
|
|
|
|
|
|
if x == 5 {
|
|
|
|
println!("x is five!");
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
If we changed the value of `x` to something else, this line would not print.
|
|
|
|
More specifically, if the expression after the `if` evaluates to `true`, then
|
|
|
|
the block is executed. If it's `false`, then it is not.
|
|
|
|
|
|
|
|
If you want something to happen in the `false` case, use an `else`:
|
|
|
|
|
|
|
|
```{rust}
|
|
|
|
let x = 5;
|
|
|
|
|
|
|
|
if x == 5 {
|
|
|
|
println!("x is five!");
|
|
|
|
} else {
|
|
|
|
println!("x is not five :(");
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
If there is more than one case, use an `else if`:
|
|
|
|
|
|
|
|
```rust
|
|
|
|
let x = 5;
|
|
|
|
|
|
|
|
if x == 5 {
|
|
|
|
println!("x is five!");
|
|
|
|
} else if x == 6 {
|
|
|
|
println!("x is six!");
|
|
|
|
} else {
|
|
|
|
println!("x is not five or six :(");
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
This is all pretty standard. However, you can also do this:
|
|
|
|
|
|
|
|
|
|
|
|
```{rust}
|
|
|
|
let x = 5;
|
|
|
|
|
|
|
|
let y = if x == 5 {
|
|
|
|
10
|
|
|
|
} else {
|
|
|
|
15
|
|
|
|
}; // y: i32
|
|
|
|
```
|
|
|
|
|
|
|
|
Which we can (and probably should) write like this:
|
|
|
|
|
|
|
|
```{rust}
|
|
|
|
let x = 5;
|
|
|
|
|
|
|
|
let y = if x == 5 { 10 } else { 15 }; // y: i32
|
|
|
|
```
|
|
|
|
|
|
|
|
This reveals two interesting things about Rust: it is an expression-based
|
|
|
|
language, and semicolons are different from semicolons in other 'curly brace
|
|
|
|
and semicolon'-based languages. These two things are related.
|
|
|
|
|
|
|
|
## Expressions vs. Statements
|
|
|
|
|
|
|
|
Rust is primarily an expression based language. There are only two kinds of
|
|
|
|
statements, and everything else is an expression.
|
|
|
|
|
|
|
|
So what's the difference? Expressions return a value, and statements do not.
|
|
|
|
In many languages, `if` is a statement, and therefore, `let x = if ...` would
|
|
|
|
make no sense. But in Rust, `if` is an expression, which means that it returns
|
|
|
|
a value. We can then use this value to initialize the binding.
|
|
|
|
|
|
|
|
Speaking of which, bindings are a kind of the first of Rust's two statements.
|
|
|
|
The proper name is a *declaration statement*. So far, `let` is the only kind
|
|
|
|
of declaration statement we've seen. Let's talk about that some more.
|
|
|
|
|
|
|
|
In some languages, variable bindings can be written as expressions, not just
|
|
|
|
statements. Like Ruby:
|
|
|
|
|
|
|
|
```{ruby}
|
|
|
|
x = y = 5
|
|
|
|
```
|
|
|
|
|
|
|
|
In Rust, however, using `let` to introduce a binding is _not_ an expression. The
|
|
|
|
following will produce a compile-time error:
|
|
|
|
|
|
|
|
```{ignore}
|
|
|
|
let x = (let y = 5); // expected identifier, found keyword `let`
|
|
|
|
```
|
|
|
|
|
|
|
|
The compiler is telling us here that it was expecting to see the beginning of
|
|
|
|
an expression, and a `let` can only begin a statement, not an expression.
|
|
|
|
|
|
|
|
Note that assigning to an already-bound variable (e.g. `y = 5`) is still an
|
|
|
|
expression, although its value is not particularly useful. Unlike C, where an
|
|
|
|
assignment evaluates to the assigned value (e.g. `5` in the previous example),
|
|
|
|
in Rust the value of an assignment is the unit type `()` (which we'll cover later).
|
|
|
|
|
|
|
|
The second kind of statement in Rust is the *expression statement*. Its
|
|
|
|
purpose is to turn any expression into a statement. In practical terms, Rust's
|
|
|
|
grammar expects statements to follow other statements. This means that you use
|
|
|
|
semicolons to separate expressions from each other. This means that Rust
|
|
|
|
looks a lot like most other languages that require you to use semicolons
|
|
|
|
at the end of every line, and you will see semicolons at the end of almost
|
|
|
|
every line of Rust code you see.
|
|
|
|
|
|
|
|
What is this exception that makes us say "almost"? You saw it already, in this
|
|
|
|
code:
|
|
|
|
|
|
|
|
```{rust}
|
|
|
|
let x = 5;
|
|
|
|
|
|
|
|
let y: i32 = if x == 5 { 10 } else { 15 };
|
|
|
|
```
|
|
|
|
|
|
|
|
Note that I've added the type annotation to `y`, to specify explicitly that I
|
|
|
|
want `y` to be an integer.
|
|
|
|
|
|
|
|
This is not the same as this, which won't compile:
|
|
|
|
|
|
|
|
```{ignore}
|
|
|
|
let x = 5;
|
|
|
|
|
|
|
|
let y: i32 = if x == 5 { 10; } else { 15; };
|
|
|
|
```
|
|
|
|
|
|
|
|
Note the semicolons after the 10 and 15. Rust will give us the following error:
|
|
|
|
|
|
|
|
```text
|
|
|
|
error: mismatched types: expected `i32`, found `()` (expected i32, found ())
|
|
|
|
```
|
|
|
|
|
|
|
|
We expected an integer, but we got `()`. `()` is pronounced *unit*, and is a
|
|
|
|
special type in Rust's type system. In Rust, `()` is _not_ a valid value for a
|
|
|
|
variable of type `i32`. It's only a valid value for variables of the type `()`,
|
|
|
|
which aren't very useful. Remember how we said statements don't return a value?
|
|
|
|
Well, that's the purpose of unit in this case. The semicolon turns any
|
|
|
|
expression into a statement by throwing away its value and returning unit
|
|
|
|
instead.
|
|
|
|
|
|
|
|
There's one more time in which you won't see a semicolon at the end of a line
|
|
|
|
of Rust code. For that, we'll need our next concept: functions.
|
|
|
|
|
|
|
|
TODO: `if let`
|