if
Two roads diverged in a yellow wood, And sorry I could not travel both And be one traveler, long I stood And looked down one as far as I could To where it bent in the undergrowth;
- Robert Frost, “The Road Not Taken”
One of the most primitive operations in computer programming is the ability to ‘branch’ between two different code paths. A program can look at a value, and then decide: should I follow this path, or should I take the other? There’s actually two different metaphors here: a tree, whose branches come from the same, single trunk. And a road, which splits off into two, each going in a different direction.
In Rust, there are a few ways to cause our code to branch. The most fundamental
way is by using if
. An if
expression gives us two paths forward, and asks
the question, “Which one should I take?”
Let’s make a new project to explore if
. Navigate to your projects directory,
and use Cargo to make a new project called branches
:
$ cargo new --bin branches
$ cd branches
Here’s a sample program using if
:
fn main() {
let condition = true;
if condition {
println!("condition was true");
} else {
println!("condition was false");
}
}
Let's try running it:
$ cargo run
Compiling branches v0.1.0 (file:///home/steve/tmp/branches)
Running `target/debug/branches`
condition was true
We can change the value of condition
:
let condition = false;
And then run it again:
$ cargo run
Compiling branches v0.1.0 (file:///home/steve/tmp/branches)
Running `target/debug/branches`
condition was false
This is the very basic structure of if
: if the condition is true, then
execute some code. If it’s not true, then execute some other code, after
else
.
An else
is not required:
fn main() {
let condition = false;
if condition {
println!("condition was true");
}
}
In this case, nothing is printed.
It’s also worth noting that condition
here must be a bool
. Let’s try an
example with something else:
fn main() {
let condition = 5;
if condition {
println!("condition was five");
}
}
If we try to run this program, Rust will complain:
Compiling branches v0.1.0 (file:///home/steve/tmp/branches)
src/main.rs:4:8: 4:17 error: mismatched types:
expected `bool`,
found `_`
(expected bool,
found integral variable) [E0308]
src/main.rs:4 if condition {
^~~~~~~~~
src/main.rs:4:8: 4:17 help: run `rustc --explain E0308` to see a detailed explanation
error: aborting due to previous error
Could not compile `branches`.
We expected a bool
, but got an integer. Rust will not automatically try to convert non-boolean types to a boolean here. We must be explicit.
else if
We can make multiple decisions by combining if
and else
in another way:
fn main() {
let number = 5;
if number == 3 {
println!("condition was 3");
} else if number == 4 {
println!("condition was 4");
} else if number == 5 {
println!("condition was 5");
} else {
println!("condition was something else");
}
}
Let's try running it:
$ cargo run
Compiling branches v0.1.0 (file:///home/steve/tmp/branches)
Running `target/debug/branches`
condition was 5
When this program executes, it will check each if
in turn, and execute the
first body for which the condition holds true.
Using a single else if
can be okay, but if you find yourself with more than one,
you may want to refactor your code. Rust has a more powerful branching construct
called match
for these cases. We'll cover it later, when we talk about enums
.
if
as an expression
There’s one last detail we need to learn about if
: it’s an expression. That means
that we can use it on the right hand side of a let
binding, for instance:
fn main() {
let condition = true;
let number = if condition {
5
} else {
6
};
println!("The value of number is: {}", number);
}
Let’s run this:
$ cargo run
Compiling branches v0.1.0 (file:///home/steve/tmp/branches)
Running `target/debug/branches`
The value of number is: 5
Remember, blocks of code evaluate to the last expression in them. And numbers
by themselves are also expressions. So in this case, the value of the whole
if
expression depends on which block of code executes.
There’s another small detail involved here: this means that if you use if
in this way, both arms of the if
must be the same type. This doesn’t work:
fn main() {
let condition = true;
let number = if condition {
5
} else {
"six"
};
println!("The value of number is: {}", number);
}
If we try to run this, we’ll get an error:
Compiling branches v0.1.0 (file:///home/steve/tmp/branches)
src/main.rs:4:18: 8:6 error: if and else have incompatible types:
expected `_`,
found `&‘static str`
(expected integral variable,
found &-ptr) [E0308]
src/main.rs:4 let number = if condition {
src/main.rs:5 5
src/main.rs:6 } else {
src/main.rs:7 "six"
src/main.rs:8 };
src/main.rs:4:18: 8:6 help: run `rustc --explain E0308` to see a detailed explanation
error: aborting due to previous error
Could not compile `branches`.
if
and else
have incompatible types. This can’t work. This also
means that you almost certainly need an else
when using if
in
this way. If you don’t, what would the value be if the condition was
false?