Result<T, E> and error handling by value
How does a function tell its caller that something went wrong? In C the usual answers are clumsy: return a special sentinel value (like -1 or NULL) that the caller must remember to check, and set a separate global variable called errno that holds the actual error code. The problems are well known — it is easy to forget the check, the real error lives in a global that the very next call can overwrite, and the type of the function tells you nothing about which errors can happen. Rust replaces all of this with a single ordinary value returned from the function: Result<T, E>.
Result<T, E> is a type with exactly two shapes, much like Option. Ok(value) means it worked and here is the result of type T; Err(error) means it failed and here is the error of type E describing what went wrong. The error travels back to the caller inside the return value itself — no hidden global, no sentinel to misread. Because a function that can fail has return type Result, the compiler makes every caller confront both outcomes: you cannot reach the Ok value without acknowledging the Err case, typically with a match. This is error handling by value: the error is a normal value you receive, inspect, and pass along, not a flag stashed somewhere off to the side.
Passing errors up by hand at every level would be tedious, so Rust adds one piece of punctuation: the ? operator. Writing let f = File::open(path)?; means if this Result is Ok, unwrap it and carry on with the value; if it is Err, immediately return that same Err from the enclosing function. So ? is propagate-the-error-or-keep-going in a single character, and it makes the common path — try, and bubble failures up to whoever can actually handle them — read almost as cleanly as code that ignores errors, while still being checked. The honest framing: Result does not make errors go away or handle them for you; it makes ignoring an error a visible choice rather than an easy accident, the way C's silent unchecked return code is. You still have to decide what to do — recover, retry, report, or give up.
use std::num::ParseIntError; fn double(s: &str) -> Result<i32, ParseIntError> { let n = s.parse::<i32>()?; // Ok -> unwrap; Err -> return it now Ok(n * 2) } match double("21") { Ok(v) => println!("got {v}"), // got 42 Err(e) => println!("bad input: {e}"), }
The error rides home inside the return value; ? unwraps Ok or returns Err for you.
Result is C's return-code-plus-errno idea made into one type the compiler forces you to inspect. The ? operator only propagates errors upward; it does not handle them, so somewhere a caller still has to decide what failure means.