Rust Backend DevelopmentError HandlingQuestion Mark OperatorResultRust

Article & Tutorial

Rust Error Handling: Result, panic!, unwrap, expect and the ? Operator

Learn robust Rust error handling with Result, panic!, unwrap, expect, error propagation, the question mark operator and custom error patterns.

Rust error handling technical illustration
Rust error handling technical illustration
Contents

Rust Learning Path — Chapter 12. Rust makes failures explicit. Instead of silently ignoring many errors, APIs commonly return Result , forcing callers to decide how failure should be handled.

Recoverable vs unrecoverable errors

Use Result for failures a caller may reasonably recover from, such as a missing file, invalid input, or network failure. panic! is for situations where continuing is not appropriate or an invariant has been violated.

Result

bash
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() { match divide(10.0, 2.0) { Ok(value) => println!("{value}"), Err(error) => eprintln!("Error: {error}"), } }

unwrap and expect

bash
let port: u16 = "8080".parse().unwrap(); let port: u16 = "8080".parse().expect("port must be a valid u16");

These are convenient when failure truly means the program cannot proceed, but application code should not use them as a substitute for deliberate error handling.

The ? operator

? returns the successful value or propagates the error to the caller.

bash
use std::fs; use std::io; fn load_config() -> Result<String, io::Error> { let content = fs::read_to_string("config.txt")?; Ok(content) }

Chaining operations safely

bash
use std::{fs, num::ParseIntError}; fn parse_port(text: &str) -> Result<u16, ParseIntError> { text.trim().parse() } fn main() { match parse_port("8080") { Ok(port) => println!("port={port}"), Err(e) => eprintln!("invalid port: {e}"), } }

Option vs Result

TypeMeaning
OptionA value is present or absent.
ResultAn operation succeeded or failed with an error value.

Custom errors

bash
#[derive(Debug)] enum ConfigError { MissingPort, InvalidPort, } fn port(value: Option<&str>) -> Result<u16, ConfigError> { let text = value.ok_or(ConfigError::MissingPort)?; text.parse().map_err(|_| ConfigError::InvalidPort) }

Mini lab: validate an application port

bash
fn validate_port(text: &str) -> Result<u16, String> { let port: u16 = text .trim() .parse() .map_err(|_| "port is not a valid number".to_string())?; if port == 0 { return Err("port cannot be zero".into()); } Ok(port) }

Best practices

  • Return errors when the caller can make a meaningful decision.
  • Add context to errors rather than hiding the original problem.
  • Use ? to keep propagation concise.
  • Reserve panic! , unwrap() , and expect() for justified cases.

Next: Chapter 13 — Generics.

Did this help?

Yes No

Join the discussion

0 Comments

Ask questions, add context, or share what worked.

Sign in to join the discussion

No comments yet. Start the discussion.

More from Melochip Editorial

Related articles

Stay in the loop. Get new Melochip articles

Receive Melochip article updates when new articles are available. Explicit opt-in only.

No pre-checked boxes. Unsubscribe support is part of the subscriber foundation.