S4.0 What This Teaches
Result<T, E> is Rust's primary mechanism for expressing operations
that can fail. Unlike exceptions, errors are values - the type system forces you to
handle them. This tutorial covers:
- The
Ok/Errvariants matchfor explicit error handling- The
?operator for propagating errors up the call chain unwrap,expect,unwrap_or- Transforming results with
map,map_err, andand_then
S4.1 The Result Type
Result<T, E> is a standard library enum:
enum Result<T, E> {
Ok(T),
Err(E),
}
Result. The caller must inspect the
variant before using the value - the compiler will not let you ignore it silently.
S4.2 Returning Result from a Function
use std::num::ParseIntError;
fn parse_positive(s: &str) -> Result<u32, String> {
let n: i32 = s.parse().map_err(|e: ParseIntError| e.to_string())?;
if n < 0 {
Err(format!("{n} is negative"))
} else {
Ok(n as u32)
}
}
u32) and the error type
(String). The body uses ? and returns Ok(...)
or Err(...) explicitly.
S4.3 Handling Result with match
match is the most explicit way to handle both outcomes. The compiler
requires both arms - you cannot accidentally use a value that might be an error:
match parse_positive("42") {
Ok(n) => println!("ok: {n}"),
Err(e) => println!("err: {e}"),
}
S4.4 The ? Operator
Result, ? propagates an
Err to the caller automatically. It eliminates repetitive
match boilerplate when you want errors to bubble up:
fn double_positive(s: &str) -> Result<u32, String> {
let n = parse_positive(s)?; // returns Err to caller if parse_positive fails
Ok(n * 2)
}
? can only appear in functions whose return type is Result
(or Option). Using it in main requires main
to return Result<(), E>.
S4.5 unwrap and expect
Result is Ok - typically in tests
or demos - unwrap extracts the value or panics on Err:
let val = parse_positive("10").unwrap();
expect is the same but adds a custom message to the panic, making the
failure site easier to diagnose:
let val = parse_positive("10").expect("should always parse");
unwrap in production code where Err is a
realistic outcome.
S4.6 unwrap_or
unwrap_or returns the inner value on Ok, or a given
default on Err:
let val = parse_positive("oops").unwrap_or(0);
println!("{val}"); // 0
S4.7 map, map_err, and and_then
Result values without unwrapping them, enabling
pipelines.
Ok value; passes
Err through:
let doubled = parse_positive("8").map(|n| n * 2);
// Ok(16)
Err value; passes
Ok through:
let result = parse_positive("xyz")
.map_err(|e| format!("parse failed: {e}"));
// Err("parse failed: invalid digit found in string")
Err:
let result = parse_positive("20")
.and_then(|n| if n < 100 { Ok(n) } else { Err(String::from("too large")) });
// Ok(20)
S4.8 Example - All Together
// Result - demonstrates Ok/Err, match, the ? operator, and common Result methods.
use std::num::ParseIntError;
fn parse_positive(s: &str) -> Result<u32, String> {
let n: i32 = s.parse().map_err(|e: ParseIntError| e.to_string())?;
if n < 0 {
Err(format!("{n} is negative"))
} else {
Ok(n as u32)
}
}
fn double_positive(s: &str) -> Result<u32, String> {
let n = parse_positive(s)?;
Ok(n * 2)
}
fn main() {
// --- match on Result ---
println!("--- match ---");
for input in &["42", "-5", "abc"] {
match parse_positive(input) {
Ok(n) => println!("ok: {n}"),
Err(e) => println!("err: {e}"),
}
}
// --- ? operator ---
println!("--- ? operator ---");
println!("{:?}", double_positive("7"));
println!("{:?}", double_positive("bad"));
// --- unwrap_or ---
println!("--- unwrap_or ---");
let val = parse_positive("oops").unwrap_or(0);
println!("unwrap_or: {val}");
// --- expect ---
println!("--- expect ---");
let val = parse_positive("10").expect("should parse");
println!("expect: {val}");
// --- map ---
println!("--- map ---");
let doubled = parse_positive("8").map(|n| n * 2);
println!("map: {doubled:?}");
// --- map_err ---
println!("--- map_err ---");
let result = parse_positive("xyz")
.map_err(|e| format!("parse failed: {e}"));
println!("map_err: {result:?}");
// --- and_then: chain a second fallible operation ---
println!("--- and_then ---");
let result = parse_positive("20")
.and_then(|n| if n < 100 { Ok(n) } else { Err(String::from("too large")) });
println!("and_then: {result:?}");
// --- is_ok / is_err ---
println!("--- is_ok / is_err ---");
println!("is_ok: {}", parse_positive("5").is_ok());
println!("is_err: {}", parse_positive("x").is_err());
}
--- match ---
ok: 42
err: -5 is negative
err: invalid digit found in string
--- ? operator ---
Ok(14)
Err("invalid digit found in string")
--- unwrap_or ---
unwrap_or: 0
--- expect ---
expect: 10
--- map ---
map: Ok(16)
--- map_err ---
map_err: Err("parse failed: invalid digit found in string")
--- and_then ---
and_then: Ok(20)
--- is_ok / is_err ---
is_ok: true
is_err: true
S4.9 Exercise
Exercise
- Write a function
safe_sqrt(x: f64) -> Result<f64, String>that returnsErrwhenxis negative andOk(x.sqrt())otherwise. Test it with positive, zero, and negative inputs. - Write a function
read_two_ints(a: &str, b: &str) -> Result<i32, String>that parses both strings and returns their sum. Use?to propagate any parse error. - Chain
safe_sqrtand a second functionsafe_log(x: f64) -> Result<f64, String>(returnsErrfor x <= 0) usingand_then. Computelog(sqrt(16.0))and print the result.
S4.10 Common Mistakes
Using unwrap in code where Err is realistic
let content = std::fs::read_to_string("config.txt").unwrap();
// panics with an unhelpful message if the file is missing
?, match, or .unwrap_or_else(|e| ...)
to handle the error meaningfully.
Returning Err with the wrong error type
fn f() -> Result<u32, String> {
let n: i32 = "x".parse()?; // parse() returns ParseIntError, not String
Ok(n as u32)
}
? requires the error type to convert into the function's declared error
type via From. Add .map_err(|e| e.to_string()) before
?, or use a boxed error type for heterogeneous errors.
Ignoring a Result
std::fs::remove_file("tmp.txt"); // warning: unused Result
Result. If you truly do not care, write
let _ = std::fs::remove_file("tmp.txt"); to silence the warning
explicitly.
S4.11 Key Terms
| Term | Meaning |
|---|---|
| Result<T, E> | Enum representing success (Ok(T)) or failure (Err(E)) |
| Ok(T) | The success variant carrying a value of type T |
| Err(E) | The failure variant carrying an error of type E |
| ? | Propagates Err to the caller; only valid in Result-returning functions |
| unwrap | Extracts the Ok value or panics on Err |
| expect | Like unwrap with a custom panic message |
| unwrap_or | Returns the Ok value or a given default on Err |
| map | Transforms the Ok value via a closure; passes Err through |
| map_err | Transforms the Err value via a closure; passes Ok through |
| and_then | Chains a second Result-returning closure on Ok; short-circuits on Err |