Day 5 · Concept 14
The ? operator
One character that propagates Err or None up the call stack. Replaces the entire "if err != nil { return nil, err }" pattern from Go. Works on Result and Option.
1 · The pattern it replaces
fn parse_and_double(s: &str) -> Result<i32, std::num::ParseIntError> {
let n = match s.parse::<i32>() {
Ok(v) => v,
Err(e) => return Err(e),
};
Ok(n * 2)
}fn parse_and_double(s: &str) -> Result<i32, std::num::ParseIntError> {
let n = s.parse::<i32>()?; // unwrap-or-return
Ok(n * 2)
}
fn main() {
println!("{:?}", parse_and_double("21"));
println!("{:?}", parse_and_double("nope"));
}2 · Chain it
use std::fs;
use std::io;
fn read_first_line(path: &str) -> io::Result<String> {
let contents = fs::read_to_string(path)?; // io::Error propagates
let first = contents.lines().next().unwrap_or("").to_string();
Ok(first)
}Each ? says "if Err, return early; if Ok, unwrap and keep going". The
function signature spells out the error type once.
3 · Error type conversion
? also calls From::from on the error, so it converts between
error types automatically — as long as a From impl exists. The Box-based
catchall trick:
fn run() -> Result<(), Box<dyn std::error::Error>> {
let n: i32 = "42".parse()?; // ParseIntError -> Box<dyn Error>
let _ = std::fs::read_to_string("x")?; // io::Error -> Box<dyn Error>
println!("{}", n);
Ok(())
}Prototyping vs production.
Box<dyn Error> in main() or scripts is fine. In libraries, define a real error enum so callers can match on variants. The next concept covers that.4 · ? on Option
fn first_char_upper(s: &str) -> Option<char> {
let c = s.chars().next()?; // None -> early return
c.to_uppercase().next()
}
fn main() {
println!("{:?}", first_char_upper("hello"));
println!("{:?}", first_char_upper(""));
}Same semantics: None in, None out. Some(v) unwraps and continues.
5 · Common mistakes
- Using
?in a function that returns(). Doesn't compile. The enclosing function must returnResultorOption. - Mixing Result and Option without a From bridge. Use
.ok_or(err)to convertOption→Resultbefore?. - main() returning (). To use
?inmain, declarefn main() -> Result<(), Box<dyn Error>>.
6 · When it clicks
- You write linear-looking code with
?at every fallible call. - You stop reaching for
.unwrap()outside tests and prototypes. - You read
?as "unwrap or propagate" without thinking.
Found this useful?