Lesson 24 of 94
Result & the ? Operator
Rust has no exceptions. An operation that can fail returns a Result, which is either a success value or an error value:
enum Result<T, E> {
Ok(T), // success, carrying a value of type T
Err(E), // failure, carrying an error of type E
}The error is part of the return type, so the caller always knows a function can fail, and the compiler warns if a Result is ignored. There is no invisible control flow: an error travels up the call stack only where you write ?.
This lesson covers everyday Result use with simple error types such as String and enums. The Error Handling section, after traits, shows how to design error types for real applications.
| Concept | Rust |
|---|---|
| Declaring that a function can fail | Result<T, E> in the return type |
| Signalling an error | return Err(X) |
| Handling an error | match result { Ok(v) => ..., Err(e) => ... } |
| Propagating an error | ? after the call |
| An unrecoverable bug | panic! |
| Parsing a number | "42".parse::<i32>() returns Result<i32, ParseIntError> |
1. A function that can fail
fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
return Err(String::from("division by zero"));
}
Ok(a / b)
}
fn main() {
println!("{:?}", divide(10.0, 4.0));
println!("{:?}", divide(1.0, 0.0));
}Ok(2.5)
Err("division by zero")2. Handling both outcomes with match
match is an ordinary expression.
fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 { Err(String::from("division by zero")) } else { Ok(a / b) }
}
fn main() {
for (a, b) in [(9.0, 3.0), (5.0, 0.0)] {
match divide(a, b) {
Ok(value) => println!("{a} / {b} = {value}"),
Err(message) => println!("could not divide {a} by {b}: {message}"),
}
}
}9 / 3 = 3
could not divide 5 by 0: division by zero3. Standard library functions return Result
parse returns Result<T, ParseIntError> (or another error type, depending on T). The error has a readable message when printed with {}.
fn main() {
let inputs = ["42", "-7", "4x2", "", "99999999999"];
for s in inputs {
match s.parse::<i32>() {
Ok(n) => println!("{s:?} -> {n}"),
Err(e) => println!("{s:?} -> error: {e}"),
}
}
}"42" -> 42
"-7" -> -7
"4x2" -> error: invalid digit found in string
"" -> error: cannot parse integer from empty string
"99999999999" -> error: number too large to fit in target type4. You can't use a Result as a value
Like Option, a Result<i32, _> is not an i32. You must decide what to do about the error first.
fn main() {
let quantity = "3".parse::<i32>();
let total = quantity * 10;
println!("{total}");
}error[E0369]: cannot multiply `Result<i32, ParseIntError>` by `{integer}`
--> src/main.rs:3:26
|
3 | let total = quantity * 10;
| -------- ^ -- {integer}
| |
| Result<i32, ParseIntError>
|
note: `Result<i32, ParseIntError>` does not implement `Mul<{integer}>`
--> /rustc/<version>/library/core/src/result.rs:557:0
|
= note: `Result<i32, ParseIntError>` is defined in another crateForgetting a Result entirely (calling a function and ignoring what it returns) produces a warning: unused Result that must be used. Failures can't be silently dropped.
5. unwrap and expect panic on Err
They take the Ok value or panic with the error. Use them in tests, examples, and when failure is truly impossible.
fn main() {
let port: u16 = "8080".parse().unwrap();
println!("port {port}");
let workers: u8 = "300".parse().expect("WORKERS must fit in a u8");
println!("{workers}");
}port 8080
thread 'main' panicked at src/main.rs:4:37:
WORKERS must fit in a u8: ParseIntError { kind: PosOverflow }6. Falling back to a default
fn main() {
let a: u32 = "12".parse().unwrap_or(1);
let b: u32 = "twelve".parse().unwrap_or(1);
let c: u32 = "twelve".parse().unwrap_or_default();
let d: u32 = "twelve".parse().unwrap_or_else(|e| {
println!(" bad input ({e}), using 10");
10
});
println!("{a} {b} {c} {d}");
} bad input (invalid digit found in string), using 10
12 1 0 10unwrap_or_else receives the error, so the fallback can log it or depend on it.
7. The ? operator propagates errors
In a function that returns Result, expr? means: "if expr is Err(e), return Err(e) from this function immediately; otherwise, give me the Ok value". The propagation is visible at each call site.
use std::num::ParseIntError;
fn sum_of(a: &str, b: &str) -> Result<i32, ParseIntError> {
let x = a.parse::<i32>()?;
let y = b.parse::<i32>()?;
Ok(x + y)
}
fn main() {
println!("{:?}", sum_of("20", "22"));
println!("{:?}", sum_of("20", "twenty-two"));
}Ok(42)
Err(ParseIntError { kind: InvalidDigit })use std::num::ParseIntError; brings a type from the standard library into scope.
8. What ? expands to
? is short for a match that returns early. Writing it out once makes it less magical.
use std::num::ParseIntError;
fn double(s: &str) -> Result<i32, ParseIntError> {
let n = match s.parse::<i32>() {
Ok(value) => value,
Err(e) => return Err(e.into()), // `?` also converts the error with `into()`
};
Ok(n * 2)
}
fn main() {
println!("{:?} {:?}", double("21"), double("x"));
}Ok(42) Err(ParseIntError { kind: InvalidDigit })The .into() conversion is what lets ? turn one error type into another, which Error Handling uses heavily.
9. ? only works in functions that return Result (or Option)
fn main() {
let n: i32 = "5".parse()?;
println!("{n}");
}error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `FromResidual`)
--> src/main.rs:2:29
|
1 | fn main() {
| --------- this function should return `Result` or `Option` to accept `?`
2 | let n: i32 = "5".parse()?;
| ^ cannot use the `?` operator in a function that returns `()`
|
help: consider adding return type
|
1 ~ fn main() -> Result<(), Box<dyn std::error::Error>> {
2 | let n: i32 = "5".parse()?;
3 | println!("{n}");
4 + Ok(())
|10. main can return a Result
When main returns Result<(), E>, you can use ? in it. Returning an Err prints Error: ... (the Debug format of the error) and exits with code 1.
fn parse_percent(s: &str) -> Result<u8, String> {
let n: u8 = s.parse().map_err(|_| format!("'{s}' is not a number"))?;
if n > 100 {
return Err(format!("{n} is more than 100"));
}
Ok(n)
}
fn main() -> Result<(), String> {
let a = parse_percent("75")?;
println!("a = {a}%");
let b = parse_percent("250")?;
println!("b = {b}%");
Ok(())
}a = 75%
Error: "250 is more than 100"The badge exits with an error marks examples like this, which end by returning an error from main. The output shows standard error too. Ok(()) means "success, with no value".
11. map_err converts the error type
? needs the error types to line up. map_err transforms the error, for example into a message with more context.
fn read_age(input: &str) -> Result<u8, String> {
let age: u8 = input
.trim()
.parse()
.map_err(|e| format!("age '{input}' is invalid: {e}"))?;
if age < 13 {
return Err(format!("age {age} is below the minimum of 13"));
}
Ok(age)
}
fn main() {
for input in [" 34 ", "abc", "300", "9"] {
println!("{:?}", read_age(input));
}
}Ok(34)
Err("age 'abc' is invalid: invalid digit found in string")
Err("age '300' is invalid: number too large to fit in target type")
Err("age 9 is below the minimum of 13")12. Mixing error types without conversion fails
Two different error types can't flow through the same ? into a Result<_, String> without conversion.
fn load(port: &str, ratio: &str) -> Result<(u16, f64), String> {
let p: u16 = port.parse()?;
let r: f64 = ratio.parse()?;
Ok((p, r))
}
fn main() {
println!("{:?}", load("80", "0.5"));
}error[E0277]: `?` couldn't convert the error to `String`
--> src/main.rs:2:30
|
1 | fn load(port: &str, ratio: &str) -> Result<(u16, f64), String> {
| -------------------------- expected `String` because of this
2 | let p: u16 = port.parse()?;
| -------^ the trait `From<ParseIntError>` is not implemented for `String`
| |
| this can't be annotated with `?` because it has type `Result<_, ParseIntError>`
|
= note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
= help: the following other types implement trait `From<T>`:
`String` implements `From<&String>`
`String` implements `From<&mut str>`
`String` implements `From<&str>`
`String` implements `From<Box<str>>`
`String` implements `From<Cow<'_, str>>`
`String` implements `From<char>`The fix is .map_err(|e| e.to_string())? on each call, or an error type that knows how to convert from both, covered in Error Handling.
13. map and and_then on Result
They work like their Option counterparts: map changes the success value, and_then chains another fallible step.
fn parse(s: &str) -> Result<i64, String> {
s.parse::<i64>().map_err(|e| e.to_string())
}
fn non_negative(n: i64) -> Result<i64, String> {
if n >= 0 { Ok(n) } else { Err(format!("{n} is negative")) }
}
fn main() {
for s in ["16", "-4", "abc"] {
let r = parse(s).and_then(non_negative).map(|n| (n as f64).sqrt());
println!("{s:>4} -> {:?}", r);
}
} 16 -> Ok(4.0)
-4 -> Err("-4 is negative")
abc -> Err("invalid digit found in string")14. Converting between Option and Result
ok_or turns None into an error. ok() turns a Result into an Option, dropping the error.
fn find_price(catalog: &[(&str, u32)], sku: &str) -> Result<u32, String> {
catalog
.iter()
.find(|(s, _)| *s == sku)
.map(|(_, price)| *price)
.ok_or(format!("unknown SKU {sku}"))
}
fn main() {
let catalog = [("KB-1", 4_999), ("MS-2", 1_999)];
println!("{:?}", find_price(&catalog, "MS-2"));
println!("{:?}", find_price(&catalog, "XX-9"));
println!("{:?}", "12".parse::<u8>().ok());
}Ok(1999)
Err("unknown SKU XX-9")
Some(12)Use ok_or_else(|| ...) when building the error is expensive, so it only happens on None.
15. ? on an Option inside a Result function
? can't mix the two directly. Convert the Option first with ok_or.
fn first_word_length(text: &str) -> Result<usize, String> {
let word = text.split_whitespace().next().ok_or("text is empty")?;
Ok(word.len())
}
fn main() {
println!("{:?}", first_word_length("hello world"));
println!("{:?}", first_word_length(" "));
}Ok(5)
Err("text is empty")ok_or("text is empty") gives a &str error, and ? converts it into the function's String error type with into().
16. Inspecting without extracting
fn main() {
let good: Result<u32, String> = Ok(3);
let bad: Result<u32, String> = Err(String::from("boom"));
println!("{} {}", good.is_ok(), bad.is_err());
println!("{:?} {:?}", good.as_ref().ok(), bad.as_ref().err());
if let Err(e) = &bad {
println!("error was: {e}");
}
println!("{}", good.is_ok_and(|n| n > 2)); // consumes `good`, so it comes last
}true true
Some(3) Some("boom")
error was: boom
trueis_ok_and takes the Result by value, like most Result and Option methods that hand you the inner value. as_ref() turns a &Result<T, E> into a Result<&T, &E> so you can inspect it without giving it up.
17. Result<(), E> for operations with no value
An operation that either succeeds or fails, with nothing to return on success, returns Result<(), E>.
fn validate_username(name: &str) -> Result<(), String> {
if name.len() < 3 {
return Err(String::from("too short"));
}
if !name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
return Err(String::from("only letters, digits, and _ are allowed"));
}
Ok(())
}
fn main() {
for name in ["ferris_99", "ab", "bad name!"] {
match validate_username(name) {
Ok(()) => println!("{name}: ok"),
Err(e) => println!("{name}: {e}"),
}
}
}ferris_99: ok
ab: too short
bad name!: only letters, digits, and _ are allowed18. Enums as error types
A String error is fine for messages, but the caller can't easily react to which error happened. An enum makes every failure case explicit and matchable.
#[derive(Debug)]
enum WithdrawError {
InsufficientFunds { needed: u64, available: u64 },
AccountFrozen,
AmountTooLarge(u64),
}
fn withdraw(balance: u64, frozen: bool, amount: u64) -> Result<u64, WithdrawError> {
if frozen {
return Err(WithdrawError::AccountFrozen);
}
if amount > 10_000 {
return Err(WithdrawError::AmountTooLarge(amount));
}
if amount > balance {
return Err(WithdrawError::InsufficientFunds { needed: amount, available: balance });
}
Ok(balance - amount)
}
fn main() {
let attempts = [(500, false, 200), (500, false, 900), (500, true, 10), (50_000, false, 20_000)];
for (balance, frozen, amount) in attempts {
match withdraw(balance, frozen, amount) {
Ok(left) => println!("ok, {left} left"),
Err(WithdrawError::InsufficientFunds { needed, available }) => {
println!("short by {}", needed - available)
}
Err(e) => println!("refused: {:?}", e),
}
}
}ok, 300 left
short by 400
refused: AccountFrozen
refused: AmountTooLarge(20000)One enum describes all the failure cases, and match makes sure every case is considered.
19. Collecting results: all or nothing
Collecting an iterator of Results into Result<Vec<T>, E> stops at the first error. This is how you parse a whole list and fail if any item is bad.
fn main() {
let good = ["1", "2", "3"];
let bad = ["1", "two", "3", "four"];
let a: Result<Vec<i32>, _> = good.iter().map(|s| s.parse::<i32>()).collect();
let b: Result<Vec<i32>, _> = bad.iter().map(|s| s.parse::<i32>()).collect();
println!("{:?}", a);
println!("{:?}", b);
}Ok([1, 2, 3])
Err(ParseIntError { kind: InvalidDigit })20. Separating successes from failures
Sometimes you want to keep going and report every failure. partition splits the results into two lists.
fn main() {
let inputs = ["10", "x", "30", "-", "50"];
let (oks, errs): (Vec<_>, Vec<_>) = inputs.iter().map(|s| s.parse::<i32>()).partition(|r| r.is_ok());
let numbers: Vec<i32> = oks.into_iter().map(Result::unwrap).collect();
println!("parsed: {:?}", numbers);
println!("{} failures", errs.len());
}parsed: [10, 30, 50]
2 failures21. ? inside a loop
? works anywhere in the function body, including loops. The first error stops the whole function.
fn total_cents(prices: &[&str]) -> Result<u64, String> {
let mut total = 0;
for (i, p) in prices.iter().enumerate() {
let (dollars, cents) = p.split_once('.').ok_or(format!("line {}: missing '.' in {p}", i + 1))?;
let d: u64 = dollars.parse().map_err(|_| format!("line {}: bad dollars in {p}", i + 1))?;
let c: u64 = cents.parse().map_err(|_| format!("line {}: bad cents in {p}", i + 1))?;
total += d * 100 + c;
}
Ok(total)
}
fn main() {
println!("{:?}", total_cents(&["4.50", "10.99", "0.51"]));
println!("{:?}", total_cents(&["4.50", "10", "0.51"]));
println!("{:?}", total_cents(&["4.50", "ten.99"]));
}Ok(1600)
Err("line 2: missing '.' in 10")
Err("line 2: bad dollars in ten.99")22. When to panic and when to return Result
- Return
Resultwhen failure is a normal, expected outcome the caller may want to handle: bad input, a missing file, a network timeout, insufficient funds. - Panic when failure means the program has a bug: an invariant was broken, an index is out of range that "can't be", an internal state is impossible.
fn parse_quantity(input: &str) -> Result<u32, String> {
// user input: failure is expected, so return a Result
input.trim().parse().map_err(|_| format!("'{input}' is not a quantity"))
}
fn unit_price(prices: &[u32], index: usize) -> u32 {
// our own table: a bad index is a bug in our code, so panicking is right
prices[index]
}
fn main() {
let prices = [250, 1_200];
match parse_quantity(" 3 ") {
Ok(q) => println!("total {}", q * unit_price(&prices, 1)),
Err(e) => println!("{e}"),
}
match parse_quantity("three") {
Ok(q) => println!("total {}", q * unit_price(&prices, 0)),
Err(e) => println!("{e}"),
}
}total 3600
'three' is not a quantity23. Realistic example: parsing a configuration file
Each error names the line it came from, so the user knows exactly what to fix.
#[derive(Debug)]
struct Config {
host: String,
port: u16,
workers: u8,
}
fn parse_config(text: &str) -> Result<Config, String> {
let mut host = None;
let mut port = None;
let mut workers = None;
for (n, raw) in text.lines().enumerate() {
let line = raw.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
let (key, value) = line.split_once('=').ok_or(format!("line {}: expected key=value", n + 1))?;
let (key, value) = (key.trim(), value.trim());
match key {
"host" => host = Some(value.to_string()),
"port" => port = Some(value.parse::<u16>().map_err(|e| format!("line {}: port: {e}", n + 1))?),
"workers" => workers = Some(value.parse::<u8>().map_err(|e| format!("line {}: workers: {e}", n + 1))?),
other => return Err(format!("line {}: unknown key '{other}'", n + 1)),
}
}
Ok(Config {
host: host.ok_or("missing 'host'")?,
port: port.unwrap_or(8080),
workers: workers.unwrap_or(4),
})
}
fn main() {
let good = "# server\nhost = example.com\nport = 443\n";
let bad_port = "host = example.com\nport = 99999";
let unknown = "host = a\ncolour = blue";
let missing = "port = 80";
for text in [good, bad_port, unknown, missing] {
println!("{:?}", parse_config(text));
}
}Ok(Config { host: "example.com", port: 443, workers: 4 })
Err("line 2: port: number too large to fit in target type")
Err("line 2: unknown key 'colour'")
Err("missing 'host'")24. Realistic example: a registration pipeline
Each step returns a Result, and ? chains them. The first failing step decides the error, and every exit point is visible.
#[derive(Debug)]
struct NewUser {
email: String,
password_hash: String,
}
fn validate_email(email: &str) -> Result<String, String> {
let email = email.trim().to_lowercase();
let (local, domain) = email.split_once('@').ok_or("email must contain '@'")?;
if local.is_empty() || !domain.contains('.') {
return Err(format!("'{email}' is not a valid email"));
}
Ok(email)
}
fn check_password(password: &str) -> Result<(), String> {
if password.len() < 12 {
return Err(String::from("password must be at least 12 characters"));
}
if !password.chars().any(|c| c.is_ascii_digit()) {
return Err(String::from("password must contain a digit"));
}
Ok(())
}
fn hash(password: &str) -> String {
format!("hash({})", password.len()) // stand-in for a real password hash
}
fn register(existing: &[&str], email: &str, password: &str) -> Result<NewUser, String> {
let email = validate_email(email)?;
if existing.contains(&email.as_str()) {
return Err(format!("{email} is already registered"));
}
check_password(password)?;
Ok(NewUser { email, password_hash: hash(password) })
}
fn main() {
let existing = ["ana@example.com"];
let attempts = [
("Ben@Example.com", "correct-horse-7"),
("ana@example.com", "another-password-1"),
("not-an-email", "whatever-123456"),
("cy@example.com", "short1"),
("dee@example.com", "no-digits-at-all"),
];
for (email, pw) in attempts {
match register(&existing, email, pw) {
Ok(user) => println!("registered {:?}", user),
Err(e) => println!("rejected: {e}"),
}
}
}registered NewUser { email: "ben@example.com", password_hash: "hash(15)" }
rejected: ana@example.com is already registered
rejected: email must contain '@'
rejected: password must be at least 12 characters
rejected: password must contain a digit25. Realistic example: parsing dimensions like 1920x1080
#[derive(Debug)]
enum DimError {
MissingSeparator,
BadNumber(String),
Zero,
}
fn parse_dimensions(s: &str) -> Result<(u32, u32), DimError> {
let (w, h) = s.split_once('x').ok_or(DimError::MissingSeparator)?;
let w: u32 = w.trim().parse().map_err(|_| DimError::BadNumber(w.to_string()))?;
let h: u32 = h.trim().parse().map_err(|_| DimError::BadNumber(h.to_string()))?;
if w == 0 || h == 0 {
return Err(DimError::Zero);
}
Ok((w, h))
}
fn main() {
for s in ["1920x1080", "800 x 600", "1024", "wide x 10", "0x100"] {
println!("{s:>10} -> {:?}", parse_dimensions(s));
}
} 1920x1080 -> Ok((1920, 1080))
800 x 600 -> Ok((800, 600))
1024 -> Err(MissingSeparator)
wide x 10 -> Err(BadNumber("wide "))
0x100 -> Err(Zero)26. Realistic example: retrying only some errors
Because errors are values, deciding what to do based on the kind of error is just a match. Here, timeouts are retried and everything else fails immediately.
#[derive(Debug)]
enum FetchError {
Timeout,
NotFound,
Unauthorized,
}
fn fetch(attempt: u32, url: &str) -> Result<String, FetchError> {
match url {
"/flaky" if attempt < 3 => Err(FetchError::Timeout),
"/flaky" | "/ok" => Ok(format!("200 OK from {url}")),
"/secret" => Err(FetchError::Unauthorized),
_ => Err(FetchError::NotFound),
}
}
fn fetch_with_retry(url: &str, max_attempts: u32) -> Result<String, FetchError> {
let mut attempt = 1;
loop {
match fetch(attempt, url) {
Err(FetchError::Timeout) if attempt < max_attempts => {
println!(" {url}: timeout on attempt {attempt}, retrying");
attempt += 1;
}
other => return other,
}
}
}
fn main() {
for url in ["/ok", "/flaky", "/secret", "/missing"] {
println!("{url}: {:?}", fetch_with_retry(url, 5));
}
println!("/flaky with 2 attempts: {:?}", fetch_with_retry("/flaky", 2));
}/ok: Ok("200 OK from /ok")
/flaky: timeout on attempt 1, retrying
/flaky: timeout on attempt 2, retrying
/flaky: Ok("200 OK from /flaky")
/secret: Err(Unauthorized)
/missing: Err(NotFound)
/flaky: timeout on attempt 1, retrying
/flaky with 2 attempts: Err(Timeout)27. Realistic example: a transfer that validates before changing anything
All checks happen first, using ?. Only when every check has passed are the balances changed, so a failure never leaves the accounts half-updated.
#[derive(Debug)]
struct Account {
id: u32,
balance: i64,
}
fn find_index(accounts: &[Account], id: u32) -> Result<usize, String> {
accounts.iter().position(|a| a.id == id).ok_or(format!("account {id} not found"))
}
fn transfer(accounts: &mut [Account], from: u32, to: u32, amount: i64) -> Result<(), String> {
if amount <= 0 {
return Err(format!("amount must be positive, got {amount}"));
}
let from_i = find_index(accounts, from)?;
let to_i = find_index(accounts, to)?;
if from_i == to_i {
return Err(String::from("cannot transfer to the same account"));
}
if accounts[from_i].balance < amount {
return Err(format!("account {from} has {} but needs {amount}", accounts[from_i].balance));
}
accounts[from_i].balance -= amount;
accounts[to_i].balance += amount;
Ok(())
}
fn main() {
let mut accounts = vec![Account { id: 1, balance: 100 }, Account { id: 2, balance: 50 }];
println!("{:?}", transfer(&mut accounts, 1, 2, 30));
println!("{:?}", transfer(&mut accounts, 1, 3, 30));
println!("{:?}", transfer(&mut accounts, 2, 1, 500));
println!("{:?}", transfer(&mut accounts, 1, 1, 5));
println!("{:?}", accounts);
}Ok(())
Err("account 3 not found")
Err("account 2 has 80 but needs 500")
Err("cannot transfer to the same account")
[Account { id: 1, balance: 70 }, Account { id: 2, balance: 80 }]position returns the index of the first element matching the closure, as an Option.
28. Realistic example: a command-line tool's main
A typical small program: main returns Result, every step uses ?, and any error ends the program with a message and a non-zero exit code, which scripts and CI systems rely on.
fn parse_args(args: &[&str]) -> Result<(String, u32), String> {
let [name, count] = args else {
return Err(format!("expected 2 arguments, got {}", args.len()));
};
let count: u32 = count.parse().map_err(|_| format!("count must be a number, got '{count}'"))?;
Ok((name.to_string(), count))
}
fn run(args: &[&str]) -> Result<(), String> {
let (name, count) = parse_args(args)?;
for i in 1..=count {
println!("{i}: hello, {name}");
}
Ok(())
}
fn main() -> Result<(), String> {
run(&["ferris", "2"])?;
run(&["ferris", "two"])?;
println!("never printed");
Ok(())
}1: hello, ferris
2: hello, ferris
Error: "count must be a number, got 'two'"Real programs read arguments with std::env::args(). Keeping the logic in a run function that takes the arguments makes it easy to test.
Summary
Result<T, E>isOk(value)orErr(error). Errors are ordinary values in the return type.- Handle results with
match,if let,unwrap_or,unwrap_or_else,map,and_then, andmap_err. ?returns the error early from a function returningResult(orOption), converting it withinto().maincan returnResult<(), E>to use?and exit with a non-zero code on error.- Convert with
ok_or(OptiontoResult) andok()(ResulttoOption). - Collect into
Result<Vec<T>, E>for all-or-nothing processing. - Use
Resultfor expected failures andpanic!for bugs.
This completes the Structs, Enums & Pattern Matching section. Next, the section on Lifetimes, which finishes the story of references.
