Chapter 04

Errors are values, not exceptions.

Rust has no try/catch for ordinary failures. A function that can fail says so in its return type, and the caller can't ignore it. It feels heavy for one day, then feels like a missing C# feature.

4.1Result<T, E>

Fallible functions return Result<T, E> — an enum that's either Ok(value) or Err(error). It's Option's cousin: where Option models "maybe absent," Result models "maybe failed, and here's why."

fn parse_port(s: &str) -> Result<u16, ParseIntError> {
    let p: u16 = s.parse()?;   // note the ?
    Ok(p)
}
Contrast

In C#, int.Parse throwing is invisible in the signature — you only learn it can fail by reading docs or getting bitten at runtime. In Rust the Result in the return type makes fallibility part of the contract the compiler enforces.

4.2The ? operator — concise error propagation

That ? is what makes Result ergonomic in practice. On a Result, it means: if Ok, unwrap the value and continue; if Err, return it from the current function immediately. It's early-return-on-error without the ceremony — the equivalent of the rethrow boilerplate you'd otherwise write in a try/catch.

C#

try {
  var cfg = ReadFile(path);
  var port = int.Parse(cfg);
  return port;
} catch (Exception e) {
  throw;  // propagate
}

Rust

fn load(path: &str) -> Result<u16, Error> {
  let cfg = read_file(path)?;
  let port = cfg.parse()?;
  Ok(port)
}

Each ? is a potential early exit. The happy path reads top-to-bottom with no nesting, and you literally cannot forget to handle an error — omitting the ? gives you a Result you have to deal with anyway.

4.3So when DO you panic?

Rust does have panic! — an unwinding abort, the nearest thing to an unhandled exception. It's for bugs and unrecoverable states, not control flow. .unwrap() and .expect("msg") panic if a Result/Option isn't the happy case.

SituationC# reflexRust idiom
User typed a bad numbercatch FormatExceptionreturn Result, handle it
File might not existcatch IOExceptionreturn Result, use ?
Invariant that "can't" be falseDebug.Assert.expect() / panic!
Startup config truly missingthrow & crash.unwrap() (fine here)
Beginner trap

It's tempting to .unwrap() everywhere to silence the compiler while learning. That's the equivalent of swallowing every exception with an empty catch — it compiles but you've thrown away the safety. Reach for ? and proper Result types in real code; save unwrap for prototypes and genuine impossibilities.

4.4Real-world error types

Hand-writing an error enum for every function is tedious, so the ecosystem standardized two crates you'll meet immediately:

CrateUse it forC# feeling
thiserrordefining your own typed error enums (libraries)custom exception classes
anyhow"just give me one error type" (apps)catching Exception at the top

The mental shift: errors flow through your code as ordinary data you can match on, store, map, and combine — not as an out-of-band stack-unwinding mechanism.