Propagating errors with ? in Rust - Time & Space Complexity
When using the ? operator in Rust, we want to know how it affects the time it takes for a function to run.
Specifically, we ask: does using ? add extra work as the input grows?
Analyze the time complexity of the following code snippet.
fn read_and_parse(input: &str) -> Result {
let num_str = input.trim();
let num = num_str.parse::()?;
Ok(num)
}
This function trims a string, tries to parse it as a number, and uses ? to return errors early.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: The
parsemethod internally processes the string characters once. - How many times: It runs once per function call, no loops or recursion here.
The time to parse grows roughly with the length of the input string, since each character is checked.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 10 character checks |
| 100 | About 100 character checks |
| 1000 | About 1000 character checks |
Pattern observation: The work grows linearly as the input string gets longer.
Time Complexity: O(n)
This means the time to run grows in a straight line with the size of the input string.
[X] Wrong: "Using ? adds extra loops or slows down the function significantly."
[OK] Correct: The ? operator just returns errors early without extra looping; it does not add repeated work.
Understanding how error handling affects performance shows you can write clean code without guessing about speed.
"What if the function called parse multiple times on different parts of the input? How would the time complexity change?"
Practice
? operator do in Rust when used with a Result type?Solution
Step 1: Understand the
The?operator behavior?operator checks if theResultis anErr. If yes, it returns the error from the current function immediately.Step 2: What happens if
If theResultisOk?ResultisOk, it unwraps the value inside and continues execution.Final Answer:
It returns the error immediately if there is one, otherwise unwraps the value. -> Option CQuick Check:
?returns error or unwraps value [OK]
? returns error early or unwraps success [OK]- Thinking
?converts Result to Option - Assuming
?ignores errors - Believing
?retries operations
? operator inside the function to propagate std::io::Error?Solution
Step 1: Identify return types compatible with
The??operator works only in functions returningResultorOption.Step 2: Check each signature
fn read_file() -> String returnsStringdirectly, no error type, so?can't be used. fn read_file() -> Result returnsResult, which supports?. fn read_file() -> () returns unit(), no error handling. fn read_file() -> Option<String> returnsOption, which supports?for absence but cannot propagatestd::io::Error.Final Answer:
fn read_file() -> Result -> Option BQuick Check:
Function must return Result or Option for?[OK]
Result or Option return type to use ? [OK]- Using
?in functions returning plain types - Forgetting to specify error type in Result
- Confusing
OptionandResultreturn types
fn parse_number(s: &str) -> Result {
let num = s.parse::<i32>()?;
Ok(num * 2)
}
fn main() {
match parse_number("10") {
Ok(n) => println!("{}", n),
Err(e) => println!("Error: {}", e),
}
}Solution
Step 1: Understand
The function tries to parse the string "10" to an integer usingparse_numberfunctions.parse::<i32>()?. Since "10" is valid, it unwraps to 10.Step 2: Calculate the returned value
It multiplies the parsed number by 2, so 10 * 2 = 20, then returnsOk(20).Step 3: Check
The match prints the number 20 because parsing succeeded.mainoutputFinal Answer:
20 -> Option AQuick Check:
Valid parse returns doubled number [OK]
? returns doubled value [OK]- Assuming parse fails and prints error
- Confusing output with input string
- Thinking code won't compile due to
?
? and fix it:fn get_first_char(s: &str) -> Result {
let first = s.chars().next()?;
Ok(first)
}Solution
Step 1: Analyze
next()return typenext()returns anOption<char>, but?requiresResultorOptioncompatible with function return.Step 2: Fix error propagation
Since function returnsResult, convertOptiontoResultusingnext().ok_or("Empty string".to_string())?to propagate error properly.Final Answer:
Changenext()tonext().ok_or("Empty string".to_string())?-> Option AQuick Check:
Convert Option to Result for?in Result function [OK]
ok_or before ? [OK]- Using
?directly on Option in Result function - Ignoring error conversion from Option to Result
- Replacing
?with.unwrap()causing panic
?. Which of the following implementations correctly uses ? to propagate errors?Solution
Step 1: Check function return type compatibility
The function returnsResult, so it can use?to propagate IO errors.Step 2: Analyze each option
fn count_lines(path: &str) -> Result { let content = std::fs::read_to_string(path)?; Ok(content.lines().count()) } uses?correctly and returnsOkwith line count. fn count_lines(path: &str) -> usize { let content = std::fs::read_to_string(path)?; content.lines().count() } returnsusizedirectly but uses?which is invalid here. fn count_lines(path: &str) -> Result { let content = std::fs::read_to_string(path).unwrap(); Ok(content.lines().count()) } usesunwrap()which panics on error, not propagates. fn count_lines(path: &str) -> Option<usize> { let content = std::fs::read_to_string(path)?; Some(content.lines().count()) } returnsOptionbutread_to_stringreturnsResult, so?cannot be used directly.Final Answer:
fn count_lines(path: &str) -> Result<usize, std::io::Error> { let content = std::fs::read_to_string(path)?; Ok(content.lines().count()) } -> Option DQuick Check:
Return Result and use?to propagate errors [OK]
? usage for error propagation [OK]- Using
?in functions without Result or Option return - Using
unwrap()instead of propagating errors - Mismatching return types and error handling
Ready to prove it?
Pass the test and this pattern moves unknown
