Skip to content

← Shape Your Data: Structs, Enums, Pattern Matching step 15 of 26

Easy Primitives

if let, while let, and let ... else

Run a tiny stack machine, then drain it.

pub fn drain_stack(ops: Vec<String>) -> Vec<i64>

Two opcodes: push <n> pushes an i64; clear empties the stack. Anything else — including a push whose argument does not parse — is skipped, not an error. When the ops are exhausted, pop the stack until it is empty and return each popped value doubled.

["push 1", "push 2", "push 3"] gives [6, 4, 2] — last in, first out.

The nested-match pyramid, and the three tools that flatten it

The starter is correct. It compiles, it passes every test, and clippy rejects it — with four separate messages, each naming a specific replacement. That is the item in one paragraph: match is a heavyweight tool, and Rust has three lighter ones for the cases where you are not really choosing between shapes.

if let — one pattern, one branch:

if let Some(rest) = op.strip_prefix("push ") {
    // rest is a &str here
}

The single_match lint is on by default and pushes you here from match x { Some(y) => ..., None => {} }. redundant_pattern_matching handles the degenerate version — if let Some(_) = o { true } else { false } is o.is_some().

while let — loop while the pattern keeps matching:

while let Some(top) = stack.pop() {
    // stops when pop() returns None
}

while_let_loop is on by default and rewrites loop { match it.next() { Some(x) => ..., None => break } } into this.

let ... else — bind irrefutably, or diverge:

let Ok(value) = rest.parse::<i64>() else {
    continue;
};

This is the biggest readability upgrade in modern Rust and it is still missing from most tutorials. After the semicolon value is an ordinary binding in the enclosing scope — not indented inside a block, not shadowed, not wrapped. The happy path stays at the left margin and the failure path gets dealt with and forgotten.

The one thing everyone gets wrong about let ... else

The else block must divergereturn, break, continue, panic!, or anything else of type !. It cannot produce a fallback value:

let Some(n) = maybe else { 0 };   // does not compile
error[E0308]: `else` clause of `let...else` does not diverge

Learners consistently expect this to be an expression, like a ternary. It is not. let ... else exists to leave; if you want a default, that is unwrap_or(0) or a plain match.

Note also that manual_let_else — the lint that would push you from a match-and-return into let ... else — is pedantic and allow-by-default. The gate will not force this idiom on you. Learn it deliberately; it is worth more than most of what the gate does enforce.

One hazard specific to while let

while let Some(x) = stack.pop() terminates because pop mutates. A while let over something that does not advance is an infinite loop — and in this harness an infinite loop is a timeout, not a clean failure message. If a submission hangs, look at your loop condition first.

Remember the grade is compile + tests + clippy -D warnings.