Let Control Flow
Rust has a few control flow constructs which differ from other languages. They are used for pattern matching:
- if letexpressions
- while letexpressions
- matchexpressions
if let expressions
The
if let expression
lets you execute different code depending on whether a value matches a pattern:
fn sleep_for(secs: f32) { let dur = if let Ok(dur) = std::time::Duration::try_from_secs_f32(secs) { dur } else { std::time::Duration::from_millis(500) }; std::thread::sleep(dur); println!("slept for {:?}", dur); } fn main() { sleep_for(-10.0); sleep_for(0.8); }
let else expressions
For the common case of matching a pattern and returning from the function, use
let else.
The “else” case must diverge (return, break, or panic - anything but falling
off the end of the block).
fn hex_or_die_trying(maybe_string: Option<String>) -> Result<u32, String> { let s = if let Some(s) = maybe_string { s } else { return Err(String::from("got None")); }; let first_byte_char = if let Some(first_byte_char) = s.chars().next() { first_byte_char } else { return Err(String::from("got empty string")); }; if let Some(digit) = first_byte_char.to_digit(16) { Ok(digit) } else { Err(String::from("not a hex digit")) } } fn main() { println!("result: {:?}", hex_or_die_trying(Some(String::from("foo")))); }
Like with if let, there is a
while let
variant which repeatedly tests a value against a pattern:
fn main() { let mut name = String::from("Comprehensive Rust 🦀"); while let Some(c) = name.pop() { println!("character: {c}"); } // (There are more efficient ways to reverse a string!) }
Here
String::pop
returns Some(c) until the string is empty, after which it will return None.
The while let lets us keep iterating through all items.
if-let
- Unlike match,if letdoes not have to cover all branches. This can make it more concise thanmatch.
- A common usage is handling Somevalues when working withOption.
- Unlike match,if letdoes not support guard clauses for pattern matching.
let-else
if-lets can pile up, as shown. The let-else construct supports flattening
this nested code. Rewrite the awkward version for students, so they can see the
transformation.
The rewritten version is:
#![allow(unused)] fn main() { fn hex_or_die_trying(maybe_string: Option<String>) -> Result<u32, String> { let Some(s) = maybe_string else { return Err(String::from("got None")); }; let Some(first_byte_char) = s.chars().next() else { return Err(String::from("got empty string")); }; let Some(digit) = first_byte_char.to_digit(16) else { return Err(String::from("not a hex digit")); }; return Ok(digit); } }
while-let
- Point out that the while letloop will keep going as long as the value matches the pattern.
- You could rewrite the while letloop as an infinite loop with an if statement that breaks when there is no value to unwrap forname.pop(). Thewhile letprovides syntactic sugar for the above scenario.