YouTip LogoYouTip

Rust Error Handle

Rust adopts a unique error handling mechanism, with no exceptions (Exception) and no try/catch. It categorizes errors into two types, handling them differently: * **Unrecoverable Errors**: Severe issues in program logic, handled by using the `panic!` macro to terminate the program. * **Recoverable Errors**: Operations that might fail but can be handled, represented by the `Result` enum. This design forces developers to handle potential errors at compile time, rather than discovering omissions at runtime. * * * ## 1. Unrecoverable Errors: panic! The `panic!` macro is used to indicate that the program has encountered a severe error from which it cannot continue. When called, it will: 1. Print an error message and the location where it occurred. 2. Unwind the call stack and clean up resources. 3. Terminate the program. ## Example fn main(){ panic!("A severe error occurred"); // The following code will never execute println!("Hello, Rust"); } Output: thread 'main' panicked at 'A severe error occurred', src/main.rs:2:5 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace. The output contains two lines of information: * **First line**: The location of the panic (file name and line number) and the error message. * **Second line**: A hint on how to view the full call stack backtrace. ### Viewing the Call Stack Backtrace Setting the `RUST_BACKTRACE=1` environment variable will display the full call stack, helping to locate the root cause of the panic: # Linux / macOS RUST_BACKTRACE=1 cargo run # Windows PowerShell $env:RUST_BACKTRACE=1; cargo run The backtrace output will list the complete call chain from the panic location to the `main` function. This information is very useful when a panic occurs deep within a call stack. > **When to use `panic!`:** For logical errors (bugs) that should not occur in the code, such as out-of-bounds access, dereferencing a null pointer, or violating an immutability contract. For expected errors caused by external input, you should use `Result`. * * * ## 2. Recoverable Errors: Result `Result` is an enum in the Rust standard library used to represent operations that might fail: enum Result<T, E> { Ok(T), // Operation succeeded, contains the result value Err(E), // Operation failed, contains error information} All functions in the Rust standard library that might fail return a `Result`. For example, opening a file: ### 2.1 Handling Result with match ## Example use std::fs::File; fn main(){ let f = File::open("hello.txt"); match f { Ok(file)=>{ println!("File opened successfully: {:?}", file); } Err(error)=>{ println!("Failed to open file: {}", error); } } } ### 2.2 Simplifying Handling with if let When you only care about the success case, `if let` is more concise than `match`: ## Example use std::fs::File; fn main(){ let f = File::open("hello.txt"); if let Ok(file)= f { println!("File opened successfully"); // Use file here ... }else{ println!("Failed to open file"); } } ### 2.3 unwrap and expect: Quick but Dangerous If you are certain the operation will not fail (or don't want to handle errors during prototyping), you can use these two shortcut methods: | Method | Behavior | Panic Message on Failure | | --- | --- | --- | | `.unwrap()` | Returns `T` on success, panics directly on failure | Uses a default error message | | `.expect("msg")` | Returns `T` on success, panics directly on failure | Uses a custom error message (easier to debug) | ## Example use std::fs::File; fn main(){ // unwrap: panics on failure, uses default message // thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: ...' let f1 = File::open("hello.txt").unwrap(); // expect: panics on failure, uses custom message (recommended) // thread 'main' panicked at 'Failed to open config file: ...' let f2 = File::open("hello.txt").expect("Failed to open config file"); } > **Recommendation:** In production code, prefer `expect` over `unwrap`, as the custom error message allows you to quickly locate the problem when a panic occurs. An even better approach is to use the `?` operator to propagate the error to the caller. * * * ## 3. Error Propagation: The ? Operator In real-world development, functions often don't want to handle errors themselves but instead **propagate** them to the caller. Rust provides the `?` operator to simplify this operation. ### 3.1 Manual Propagation vs. the ? Operator First, look at the manual propagation approachβ€”verbose but clear: ## Example use std::fs::File; use std::io::{self, Read}; // Manually propagating errors (verbose) fn read_file_manual(path:&str)-> Result{ let f = File::open(path); // If opening fails, return Err let mut file =match f { Ok(file)=> file, Err(e)=>return Err(e),// Return the error early }; let mut content = String::new(); // If reading fails, return Err match file.read_to_string(&mut content){ Ok(_)=> Ok(content), Err(e)=> Err(e), } } Using the `?` operator, the same logic can be simplified to: ## Example use std::fs::File; use std::io::{self, Read}; // Using the ? operator (concise) fn read_file(path:&str)-> Result{ let mut file = File::open(path)?;// Automatically returns Err on failure let mut content = String::new(); file.read_to_string(&mut content)?;// Automatically returns Err on failure Ok(content) } You can also chain calls for further simplification: fn read_file(path: &str) -> Result<String, io::Error> { let mut content = String::new(); File::open(path)?.read_to_string(&mut content)?; Ok(content)} How the `?` operator works: > **Important Limitation:** The `?` operator can only be used in functions that return `Result` (or `Option`). Starting from Rust 1.39, the `main` function can also return a `Result`. ### 3.2 Using ? in main The default `main` function returns `()` and cannot use `?`. However, you can make `main` return a `Result`: ## Example use std::fs::File; use std::io::{self, Read}; fn read_file(path:&str)-> Result{ let mut content = String::new(); File::open(path)?.read_to_string(&mut content)?; Ok(content) } // main returns Result, so ? can be used inside main fn main()-> Result<(), Box>{ let content = read_file("hello.txt")?;// ? can now be used in main println!("{}", content); Ok(()) } * * * ## 4. Custom Error Types and Categorized Handling In real projects, you often need to handle different error types differently. Rust achieves this through the `kind()` method: ## Example use std::fs::File; use std::io::{self, Read}; // Encapsulate file reading into a separate function, propagating errors with ? fn read_text_from_file(path:&str)-> Result{ let mut f = File::open(path)?; let mut s = String::new(); f.read_to_string(&mut s)?; Ok(s) } fn main(){ match read_text_from_file("hello.txt"){ Ok(content)=> println!("File content:n{}", content), Err(e)=>{ // Handle differently based on error type match e.kind(){ io::ErrorKind::NotFound=>{ println!("File not found, please check the path"); } io::ErrorKind::PermissionDenied=>{ println!("No permission to read this file"); } _ =>{ println!("Error reading file: {}", e); } } } } } Output (when the file does not exist): File not found, please check the path Common variants of `io::ErrorKind`: | ErrorKind | Meaning | | --- | --- | | `NotFound` | File or directory does not exist | | `PermissionDenied` | Insufficient permissions | | `AlreadyExists` | File already exists (during creation) | | `ConnectionRefused` | Connection refused | | `TimedOut` | Operation timed out | | `InvalidInput` | Invalid parameter | * * * ## 5. Custom Error Types In projects, you often need to define your own error types to represent errors in business logic: ## Example use std::fmt; use std::num::ParseIntError; // Define a custom error enum #[derive(Debug)] enum AppError { IoError(std::io::Error), ParseError(ParseIntError), CustomError(String), } // Implement the Display trait for formatted output impl fmt::Display for AppError { fn fmt(&self, f:&mut fmt::Formatter)-> fmt::Result{ match self{ AppError::IoError(e)=> write!(f,"IO Error: {}", e), AppError::ParseError(e)=> write!(f,"Parse Error: {}", e), AppError::CustomError(msg)=> write!(f,"Business Error: {}", msg), } } } // Implement the From trait to allow automatic error type conversion with the ? operator impl Fromfor AppError { fn from(error: std::io::Error)-> Self { AppError::IoError(error) } } impl Fromfor AppError { fn from(error: ParseIntError)-> Self { AppError::ParseError(error) } } // Now you can handle different error types in the same function using ? fn process_config(path:&str)-> Result{ let content = std::fs::read_to_string(path)?;// io::Error β†’ AppError let value:i32= content.trim().parse()?;// ParseIntError β†’ AppError if value println!("Config value: {}", val), Err(e)=> println!("Error: {}", e), } } > **Third-party Library Recommendation:** In real projects, you can use the `thiserror` library to automatically derive `Display` and `From` implementations, significantly reducing boilerplate code. For application-layer code, the `anyhow` library provides a convenient `anyhow::Result` type, suitable for rapid development. * * * ## 6. Option: Values That Might Not Exist Besides `Result`, Rust has another important enum for handling "values that might not exist"β€”`Option`: enum Option<T> { Some(T), // Has a value None, // No value} `Option` is used to replace `null` from other languages. Rust has no `null`; any value that might be empty must be wrapped in an `Option`: ## Example fn find_user(id:u32)-> Option{ match id { 1=> Some("Alice".to_string()), 2=> Some("Bob".to_string()), _ => None,// User does not exist } } fn main(){ // Handle Option using match match find_user(1){ Some(name)=> println!("Found user: {}", name), None => println!("User does not exist"), } // Simplify with if let if let Some(name)= find_user(99){ println!("Found user: {}", name); }else{ println!("User does not exist"); } // unwrap_or provides a default value let name = find_user(99).unwrap_or("Anonymous User".to_string()); println!("Username: {}", name);// Anonymous User // The ? operator also works with Option let first_char = get_first_char("hello"); println!("First character: {:?}", first_char);// Some('h') } fn get_first_char(s:&str)-> Option{ s.chars().next()// Returns Option } Comparison between `Option` and `Result`: | Comparison Item | Option | Result | | --- | --- | --- | | Purpose | Value may or may not exist | Operation may succeed or fail | | Success | `Some(T)` | `Ok(T)` | | Failure | `None` (no additional information) | `Err(E)` (contains the reason for failure) | | Typical Scenarios | Lookups, optional fields, default values | File operations, network requests, parsing | | Conversion | `ok_or(err)` β†’ Result | `ok()` β†’ Option | * * * ## Summary | Scenario | Recommended Approach | Explanation | | --- | --- | --- | | Program encounters an unfixable bug | `panic!("reason")` | Terminates the program, used for situations that should not occur | | Operation might fail | Return `Result` | Forces the caller to handle the error | | Propagate errors within a function | `?` operator | Automatically returns `Err` on failure, extracts the value on success | | Quick prototyping / testing | `.expect("reason")` | Panics on failure, but with a clear error message | | Value might not exist | `Option` | Uses `Some` / `None` to replace null | | Handle different error types separately | `e.kind()` | Matches specific error variants | | Custom error types | Implement `Display` + `From` | Enables automatic conversion with `?` | > Rust's error handling philosophy: **Errors are part of the type system, not exceptions to the control flow**. The compiler forces you to handle every possible error scenario, making your program more reliable at runtime.
← Os LchmodOs Getcwd β†’