Skip to content
Result/Option Chaining and ?
step 1/7

Reading — step 1 of 7

Learn

~3 min readIterators, Result, Option

Result/Option Chaining and ?

Rust has no exceptions. Fallibility is encoded in ordinary values:

  • Option<T>Some(T) or None: "might be missing"
  • Result<T, E>Ok(T) or Err(E): "might fail, and here's why"

Because they appear in the type signature, the compiler forces every caller to deal with them. No invisible control flow, no forgotten catch blocks.

Handling: match, if let, combinators

let maybe: Option<i32> = Some(5);

match maybe {
    Some(n) => println!("got {}", n),
    None => println!("nothing"),
}

if let Some(n) = maybe {        // when you only care about one arm
    println!("got {}", n);
}

For pipeline-style code, combinators beat nested matches:

let v = maybe.unwrap_or(0);                      // 5, or the default on None
let v = maybe.unwrap_or_else(|| expensive());    // lazily computed default
let v = maybe.map(|n| n * 2);                    // Some(10) — transform inside
let v = maybe.and_then(|n| checked_div(100, n)); // chain a fallible step

The trap: unwrap()

.unwrap() means "crash if this failed":

let n: i32 = "abc".parse().unwrap();
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value:
ParseIntError { kind: InvalidDigit }'

A panic aborts the program with a nonzero exit code and nothing useful on stdout. Fine in a throwaway script; in graded exercises and production code it's exactly the failure mode you were supposed to handle.

The idiom: ? propagates

? unwraps the success case and early-returns the failure to the caller:

fn parse_two(a: &str, b: &str) -> Result<i32, std::num::ParseIntError> {
    let x = a.parse::<i32>()?;   // Err? return it. Ok? unwrap into x
    let y = b.parse::<i32>()?;
    Ok(x + y)
}

Each ? desugars to roughly:

let x = match a.parse::<i32>() {
    Ok(v) => v,
    Err(e) => return Err(From::from(e)),  // error types convert via From
};

Division of labor: parse_two propagates with ?; its caller decides with match. That's the shape of most real Rust code — deep functions bubble errors up, and one place near the top turns them into user-facing behavior.

? only works inside a function that returns Result or Option. Use it in a fn main() that returns () and you get:

error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option`

Collecting many Results into one

let nums: Result<Vec<i32>, _> = strings.iter()
    .map(|s| s.parse::<i32>())
    .collect();    // the first Err short-circuits the whole collect

Your exercise

Implement parse_two(a, b) using ? on both parses and returning Ok(x + y). main is already written: it matches your Result and prints sum: <n> on Ok, or error: invalid input on Err.

TEST 1 feeds 3 then 4 and expects exactly:

sum: 7

Mistakes the grader catches:

  • Leaving the placeholder a.parse::<i32>() as the return value — it ignores b entirely, so TEST 1 prints sum: 3 instead of sum: 7.
  • Using .unwrap() instead of ? — TEST 3 feeds abc and expects error: invalid input; unwrap panics, the program dies, and the test sees no output at all. The whole point is returning the Err so main's match can handle it.
  • Changing main's strings — the grader matches sum: 7 (colon, one space) and error: invalid input byte for byte.
  • TEST 4 feeds -5 then 5 and expects sum: 0parse::<i32>() handles the minus sign; no special-casing needed.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…