Syntax Cache
BlogMethodFeaturesHow It WorksBuild a Game
  1. Home
  2. Compare
  3. Python vs Rust
  4. Python vs Rust Error Handling

Python vs Rust Error Handling

Exception-based error handling in Python versus Rust's type-based Result<T, E> and Option<T> system.

Exceptions vs Result Types

Python handles errors by throwing and catching exceptions. A function that can fail raises an exception, and any caller in the chain can catch it with try/except. The exception propagates invisibly up the call stack until someone handles it or the program crashes. Rust rejects this model entirely. Functions that can fail return Result<T, E>, a type that is either Ok(value) or Err(error). The caller must explicitly handle both possibilities -- the compiler refuses to let you use the Ok value without acknowledging that an Err might be there. This is the single largest conceptual shift for Python developers learning Rust. In Python, any function call can secretly throw; in Rust, fallibility is encoded in the return type. If a function returns i32, it never fails. If it returns Result<i32, ParseError>, it might fail, and the signature tells you so. The trade-off is verbosity: every fallible call requires a match, an unwrap, or the ? operator. But the benefit is that error handling paths are never accidentally skipped -- a common source of production bugs in exception-based languages where a missing try/except means the error flies past silently (or crashes unexpectedly).

Parse a number

Python
try:
    n = int("42")
except ValueError:
    n = 0
Rust
let n: i32 = "42".parse().unwrap_or(0);

Explicit match on Result

Python
try:
    n = int(user_input)
    print(f"Got: {n}")
except ValueError as e:
    print(f"Invalid: {e}")
Rust
match user_input.parse::<i32>() {
    Ok(n) => println!("Got: {n}"),
    Err(e) => println!("Invalid: {e}"),
}
Warm-up1 / 2

Can you write this from memory?

Write a try/except skeleton that catches Exception. Use pass in both blocks.

Python: Python Exception Handling Practice: try/except/else/finally, raise from, custom exceptions →

The ? Operator

Rust's ? operator is syntactic sugar for early return on error. Placing ? after a Result expression unwraps the Ok value if present, or returns the Err from the current function immediately. This replaces the verbose match arm that would otherwise appear on every fallible call. Before ? was stabilized (Rust 1.13), developers used the try!() macro for the same purpose, and older codebases still reference it. Python has no equivalent -- exceptions propagate automatically, so there is no syntax for "unwrap or return the error." The closest Python analogue is not catching an exception and letting it propagate, which happens by default. The asymmetry is telling: Python requires explicit code to stop error propagation (try/except), while Rust requires explicit code to allow it (?). This means Rust functions that propagate errors are visually marked at every call site, making error flow readable without jumping to callee definitions. The ? operator also works with Option<T>, returning None from the function if the value is None. This makes it useful for chains of nullable lookups: config.get("db")?.get("host")?.as_str().

? operator for early return

Python
def read_config(path):
    # exceptions propagate automatically
    with open(path) as f:
        return json.load(f)
    # FileNotFoundError or JSONDecodeError
    # bubble up if not caught
Rust
fn read_config(path: &str) -> Result<Config, Box<dyn Error>> {
    let text = std::fs::read_to_string(path)?;
    let config: Config = serde_json::from_str(&text)?;
    Ok(config)
    // ? returns Err early if either call fails
}

? with Option

Python
# dict.get returns None if missing
host = config.get("db", {}).get("host")
if host is None:
    return None
Rust
let host = config
    .get("db")?
    .get("host")?
    .as_str();

unwrap, expect, and When to Panic

Rust provides .unwrap() to extract the Ok value from a Result or the Some value from an Option, panicking if the value is Err or None. .expect("message") does the same but with a custom panic message. Python developers use these early and often when prototyping because they work like Python's assumption that operations succeed. The Rust community treats unwrap in production code as a code smell: it means you chose to crash instead of handle the error. The idiomatic progression is to start with unwrap during development, then replace each one with proper handling (?, match, or a default value) before shipping. expect is acceptable when the programmer can prove the error case is unreachable -- for example, a regex compiled from a literal string: Regex::new(r"\d+").expect("regex is valid"). The analogy in Python is an assert statement that should never fire. Rust also provides unwrap_or(default), unwrap_or_else(|| compute()), and unwrap_or_default() for supplying fallback values without panicking. These map roughly to Python's pattern of try/except with a default in the except block, or dict.get(key, default).

unwrap vs expect

Python
# Python: just use it, exception if wrong
n = int("42")  # ValueError if not a number
Rust
// unwrap: panic on error
let n: i32 = "42".parse().unwrap();

// expect: panic with custom message
let n: i32 = "42".parse()
    .expect("should be a valid integer");

Fallback values

Python
n = int(s) if s.isdigit() else 0
# or
try:
    n = int(s)
except ValueError:
    n = 0
Rust
let n: i32 = s.parse().unwrap_or(0);

// or compute fallback lazily
let n: i32 = s.parse()
    .unwrap_or_else(|_| compute_default());

Custom Error Types

Python custom exceptions inherit from Exception: class NotFoundError(Exception): pass. Rust custom errors implement the std::error::Error trait and typically use an enum to represent multiple failure modes: enum AppError { NotFound, Unauthorized, Timeout }. Each variant can carry data: NotFound(String) holds the missing resource name. The thiserror crate automates the boilerplate (Display and Error trait implementations) with derive macros, reducing a 20-line impl block to a few annotations. The anyhow crate takes a different approach: it provides a single anyhow::Error type that wraps any error, similar to Python's broad except Exception. anyhow is popular in application code where you want to propagate errors without defining custom types, while thiserror is standard for libraries that export typed error hierarchies. Python's exception hierarchy (ValueError, TypeError, IOError) maps conceptually to Rust enum variants, but the mechanics differ. Python catches by type (except ValueError), Rust matches by variant (Err(AppError::NotFound(resource))). Both approaches let you handle specific failures while propagating unexpected ones, but Rust's pattern matching is exhaustive -- the compiler warns if you forget a variant.

Custom error type

Python
class AppError(Exception):
    pass

class NotFoundError(AppError):
    def __init__(self, resource):
        self.resource = resource
        super().__init__(f"{resource} not found")
Rust
use thiserror::Error;

#[derive(Debug, Error)]
enum AppError {
    #[error("{0} not found")]
    NotFound(String),
    #[error("unauthorized")]
    Unauthorized,
}

Handling specific variants

Python
try:
    item = find(name)
except NotFoundError:
    print("Missing resource")
except AppError:
    print("Other app error")
Rust
match find(name) {
    Ok(item) => use_item(item),
    Err(AppError::NotFound(_)) => {
        println!("Missing resource");
    }
    Err(e) => println!("Other error: {e}"),
}
Warm-up1 / 2

Can you write this from memory?

Write a try/except skeleton that catches Exception. Use pass in both blocks.

Python: Python Exception Handling Practice: try/except/else/finally, raise from, custom exceptions →

Practice Both Languages

10 free exercises a day. No credit card required. Build syntax muscle memory with spaced repetition.

Free forever. No credit card required.

← Back to Python vs Rust
Syntax Cache

Build syntax muscle memory with spaced repetition.

Product

  • Pricing
  • Our Method
  • Daily Practice
  • Design Patterns
  • Interview Prep

Resources

  • Blog
  • Compare
  • Cheat Sheets
  • Vibe Coding
  • Muscle Memory

Languages

  • Python
  • JavaScript
  • TypeScript
  • Rust
  • SQL
  • GDScript

Legal

  • Terms
  • Privacy
  • Contact

© 2026 Syntax Cache

Cancel anytime in 2 clicks. Keep access until the end of your billing period.

No refunds for partial billing periods.