if-let and while-let
Tutorial Video
Text description (accessibility)
This video demonstrates the "if-let and while-let" functional Rust example. Difficulty level: Fundamental. Key concepts covered: Functional Programming. `match` requires handling all cases, which is verbose when you only care about one. Key difference from OCaml: 1. **Conciseness**: `if let` is more concise than `match` for single
Tutorial
The Problem
match requires handling all cases, which is verbose when you only care about one. if let provides single-arm matching: execute a block only when the pattern matches, with an optional else for the non-matching case. while let loops while a pattern continues to match, perfect for draining queues, popping stacks, and processing iterators. These constructs are ubiquitous in real Rust code — understanding them and knowing when to prefer them over match is essential.
🎯 Learning Outcomes
if let Some(n) = opt { ... } binds and branches in one expressionif let chains work and when to prefer them over matchwhile let Some(x) = queue.pop() { ... } processes collections until emptyif let handles enum variants, Result, and custom patternsmatches! vs if let vs match for different use casesCode Example
#![allow(clippy::all)]
//! if-let and while-let
//!
//! Conditional pattern matching.
/// Basic if-let.
pub fn describe_option(opt: Option<i32>) -> String {
if let Some(n) = opt {
format!("Got: {}", n)
} else {
"Nothing".to_string()
}
}
/// if-let with else-if-let.
pub fn categorize(opt: Option<i32>) -> &'static str {
if let Some(n) = opt {
if n > 0 {
"positive"
} else if n < 0 {
"negative"
} else {
"zero"
}
} else {
"none"
}
}
/// if-let with enum.
#[derive(Debug)]
pub enum Message {
Text(String),
Number(i32),
Empty,
}
pub fn extract_text(msg: &Message) -> Option<&str> {
if let Message::Text(s) = msg {
Some(s)
} else {
None
}
}
/// while-let for iteration.
pub fn sum_stack(mut stack: Vec<i32>) -> i32 {
let mut sum = 0;
while let Some(n) = stack.pop() {
sum += n;
}
sum
}
/// Combining if-let with guards.
pub fn check_value(opt: Option<i32>) -> &'static str {
if let Some(n) = opt {
if n > 100 {
"large"
} else {
"small"
}
} else {
"none"
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_describe() {
assert!(describe_option(Some(5)).contains("Got"));
assert!(describe_option(None).contains("Nothing"));
}
#[test]
fn test_categorize() {
assert_eq!(categorize(Some(5)), "positive");
assert_eq!(categorize(Some(-3)), "negative");
assert_eq!(categorize(None), "none");
}
#[test]
fn test_extract_text() {
let msg = Message::Text("hello".into());
assert_eq!(extract_text(&msg), Some("hello"));
assert_eq!(extract_text(&Message::Empty), None);
}
#[test]
fn test_sum_stack() {
assert_eq!(sum_stack(vec![1, 2, 3, 4, 5]), 15);
assert_eq!(sum_stack(vec![]), 0);
}
#[test]
fn test_check_value() {
assert_eq!(check_value(Some(200)), "large");
assert_eq!(check_value(Some(50)), "small");
}
}Key Differences
if let is more concise than match for single-arm cases; OCaml's match requires both arms but is equally concise.while let is idiomatic for draining collections; OCaml uses recursive functions or while loops with mutable state.if let A = x && let B = y { ... } for chaining pattern checks; OCaml uses match nesting or Option.bind.matches! macro**: Rust's matches!(val, Pattern) is the most concise boolean check; OCaml uses a helper function or match val with Pat -> true | _ -> false.OCaml Approach
OCaml uses match with a single relevant arm:
(* if let equivalent *)
let describe_option opt =
match opt with
| Some n -> Printf.sprintf "Got: %d" n
| None -> "Nothing"
(* while let equivalent: functional recursion *)
let rec drain queue =
match Queue.pop_opt queue with
| None -> ()
| Some item -> process item; drain queue
Full Source
#![allow(clippy::all)]
//! if-let and while-let
//!
//! Conditional pattern matching.
/// Basic if-let.
pub fn describe_option(opt: Option<i32>) -> String {
if let Some(n) = opt {
format!("Got: {}", n)
} else {
"Nothing".to_string()
}
}
/// if-let with else-if-let.
pub fn categorize(opt: Option<i32>) -> &'static str {
if let Some(n) = opt {
if n > 0 {
"positive"
} else if n < 0 {
"negative"
} else {
"zero"
}
} else {
"none"
}
}
/// if-let with enum.
#[derive(Debug)]
pub enum Message {
Text(String),
Number(i32),
Empty,
}
pub fn extract_text(msg: &Message) -> Option<&str> {
if let Message::Text(s) = msg {
Some(s)
} else {
None
}
}
/// while-let for iteration.
pub fn sum_stack(mut stack: Vec<i32>) -> i32 {
let mut sum = 0;
while let Some(n) = stack.pop() {
sum += n;
}
sum
}
/// Combining if-let with guards.
pub fn check_value(opt: Option<i32>) -> &'static str {
if let Some(n) = opt {
if n > 100 {
"large"
} else {
"small"
}
} else {
"none"
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_describe() {
assert!(describe_option(Some(5)).contains("Got"));
assert!(describe_option(None).contains("Nothing"));
}
#[test]
fn test_categorize() {
assert_eq!(categorize(Some(5)), "positive");
assert_eq!(categorize(Some(-3)), "negative");
assert_eq!(categorize(None), "none");
}
#[test]
fn test_extract_text() {
let msg = Message::Text("hello".into());
assert_eq!(extract_text(&msg), Some("hello"));
assert_eq!(extract_text(&Message::Empty), None);
}
#[test]
fn test_sum_stack() {
assert_eq!(sum_stack(vec![1, 2, 3, 4, 5]), 15);
assert_eq!(sum_stack(vec![]), 0);
}
#[test]
fn test_check_value() {
assert_eq!(check_value(Some(200)), "large");
assert_eq!(check_value(Some(50)), "small");
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_describe() {
assert!(describe_option(Some(5)).contains("Got"));
assert!(describe_option(None).contains("Nothing"));
}
#[test]
fn test_categorize() {
assert_eq!(categorize(Some(5)), "positive");
assert_eq!(categorize(Some(-3)), "negative");
assert_eq!(categorize(None), "none");
}
#[test]
fn test_extract_text() {
let msg = Message::Text("hello".into());
assert_eq!(extract_text(&msg), Some("hello"));
assert_eq!(extract_text(&Message::Empty), None);
}
#[test]
fn test_sum_stack() {
assert_eq!(sum_stack(vec![1, 2, 3, 4, 5]), 15);
assert_eq!(sum_stack(vec![]), 0);
}
#[test]
fn test_check_value() {
assert_eq!(check_value(Some(200)), "large");
assert_eq!(check_value(Some(50)), "small");
}
}
Deep Comparison
OCaml vs Rust: pattern if let
See example.rs and example.ml for implementations.
Exercises
fn collect_stack(v: &mut Vec<i32>) -> Vec<i32> using while let Some(x) = v.pop() to collect elements in reverse order.if let checks that extract values from nested Option<Option<i32>> — compare with a let-else version.fn keep_moves(events: Vec<Event>) -> Vec<(i32, i32)> using if let Event::Move { x, y } = e { ... } in a loop to collect only move events.