Skip to content

← Errors Are Values step 11 of 24

Easy Primitives

Propagating with ?

Refactor pipeline(input: String) -> Result<i32, String> to propagate errors with ?. Keep the four supplied helpers unchanged.

Required behavior

Trim whitespace from both ends of the original input once. Then run these steps in order, stopping at the first failure and returning its exact message:

Step Success Failure message
non_empty The trimmed text is nonempty empty input
strip_tag Remove the exact lowercase prefix n= missing `n=` prefix in `TEXT`
to_number Parse the remaining text as an i32 not a number: TEXT
in_range The number is between 1 and 1000, inclusive out of range: NUMBER

Here TEXT is the text that reached that helper, and NUMBER is the parsed integer. Preserve the messages, including their spaces and backticks.

For example, " n=42 " returns Ok(42), "42" returns Err("missing `n=` prefix in `42`"), and "n=5000" returns Err("out of range: 5000").

Do not trim again after removing the prefix. "n= 42" fails parsing with "not a number: 42", which has two spaces after the colon. "n=" produces "not a number: ", including the trailing space.

Parsing accepts a leading + or - and leading zeroes, but not decimal points, underscores or non-ASCII digits. Thus "n=+0042" succeeds, "n=-1" reaches the range check, and "n=2147483648" fails parsing because it exceeds i32::MAX.

Replace the propagation matches

The starter computes the right results, but Clippy rejects its redundant matches when warnings are denied. The first three matches trigger question_mark: each extracts an Ok value or immediately returns the error.

For a Result, expr? produces the value inside Ok. On Err, it returns early from the enclosing function or closure, converting the error through From when needed. Here every helper and pipeline uses String, so propagation preserves the error without reformatting it.

Replace those three matches with ?. The last match merely rebuilds the same Result and triggers needless_match; return in_range(n) directly. Three bindings followed by that final expression make a clear four-line body. The tests do not count source lines.

Writing Ok(in_range(n)?) also works at runtime, but triggers needless_question_mark here because the inner result already has the function's return type. In other code, an error conversion or additional success-path work can make ? necessary.

Where an early return goes

An ordinary block does not create a new return boundary. A closure does. This complete example parses each input independently, so a failure does not stop the surrounding iteration:

fn main() {
    let results: Vec<Result<i32, std::num::ParseIntError>> =
        ["2", "bad", "4"]
            .into_iter()
            .map(|text| {
                let n = text.parse::<i32>()?;
                Ok(n * 2)
            })
            .collect();

    assert_eq!(results[0], Ok(4));
    assert!(results[1].is_err());
    assert_eq!(results[2], Ok(8));
}

Use match when you need to recover differently for particular failures, or map_err to transform an error before propagating it. Do neither in this exercise: its contract requires the helper messages unchanged.

Choosing error types

String keeps this exercise focused on propagation. It can contain descriptive information and its text can be inspected, but callers would have to compare or parse messages to distinguish error categories. A dedicated error enum gives those categories explicit variants and fields.

The lessons on implementing the Error trait and From conversions develop that approach.

The submission must compile, pass the output cases and pass Clippy with warnings denied.