Rust's philosophy around error handling distinguishes between *recoverable* errors and *unrecoverable* errors. The `panic!` macro is Rust's way of dealing with *unrecoverable* errors. When `panic!` is called, the program will print a failure message, unwind the stack (cleaning up data it encounters), and then exit. Alternatively, it can be configured to immediately abort without unwinding.
When to `panic!`:
`panic!` should generally be reserved for situations where a bug in your code has put the program into an inconsistent or impossible state, and there's no reasonable way for the program to continue or recover.
1. Logic Bugs: If an invariant that your code relies upon is violated, indicating a bug in your own logic. For example, if you have a state machine and it somehow reaches a state that you've explicitly coded as "impossible".
2. Unreachable Code: The `unreachable!()` macro is a specialized panic that signals a code path that should logically never be reached.
3. Prototypes/Temporary Code: In early development or scripts, `unwrap()` and `expect()` (which panic on `None` or `Err`) can be used for quick error handling, with the intention of replacing them with robust `Result` handling later.
4. Testing: Panics are often used in tests to signal that an assertion has failed.
5. Developer Experience (DEX): Sometimes, panicking early when a programming error occurs can provide clearer debugging information than attempting to limp along with corrupted state.
When *not* to `panic!` (and use `Result<T, E>` instead):
For errors that are *expected* to occur and for which a caller might reasonably want to react and continue execution, `panic!` is the wrong tool. These are *recoverable* errors.
1. Invalid User Input: If a user provides malformed input, the program should gracefully report the issue, not crash.
2. File I/O Errors: A file might not exist, permissions might be wrong, or the disk might be full. These are external conditions the program can often react to.
3. Network Errors: A server might be down, the connection might drop.
4. External System Failures: Database connectivity issues, API rate limits, etc.
5. Library APIs: A well-designed library should almost always return `Result` for potential failures, allowing the application developer to decide how to handle them. Forcing a panic means the library takes control of the application's fate.
Implications of `panic!`:
* Unwinding vs. Aborting: By default, Rust unwinds the stack, cleaning up resources. This incurs some runtime overhead. You can configure your program to `abort` on panic, which is faster but doesn't clean up memory (can be useful for embedded systems or when memory safety on unwind is not guaranteed).
* Program Termination: A panic typically means the current process will exit.
* Boundaries: While `panic!` is generally for unrecoverable errors, you can use `std::panic::catch_unwind` to attempt to catch a panic at a *thread boundary* (e.g., when interfacing with FFI or when a library panics), but this is an advanced use case and usually not recommended for general application logic.
Best Practices:
* Favor `Result<T, E>` for recoverable errors: This is the idiomatic Rust way to handle errors.
* Use `panic!` for internal logic errors/bugs: When a state is reached that should be impossible according to your program's design.
* Document `panic!` behavior: If a public function in your library can panic, document it clearly so users know what to expect.
* Avoid `panic!` in library APIs: Unless the panic signals a bug in the *caller's* usage of the API, return a `Result`.
Example Code
```rust
use std::fs;
use std::io::{self, Read};
// --- Appropriate use of panic! (for a bug/logic error) ---
fn get_lucky_number(is_admin: bool) -> u32 {
if is_admin {
// This is a contrived example. In a real scenario, if `lucky_number_source`
// was a file or database, you'd use Result. But here, let's assume
// for admins, we *guarantee* a value, and its absence is a bug.
// `unwrap()` is used here to signal a critical, unrecoverable internal logic error.
Some(777).unwrap()
} else {
None.expect("Non-admin users should not call get_lucky_number without checking")
// This 'expect' here is used to demonstrate how `expect` also panics.
// In reality, you'd likely return an Option or Result for non-admins,
// or handle the `is_admin` check earlier.
}
}
// --- Using Result for recoverable errors (idiomatic Rust) ---
fn read_file_contents(path: &str) -> Result<String, io::Error> {
let mut file = fs::File::open(path)?; // The '?' operator propagates the error
let mut contents = String::new();
file.read_to_string(&mut contents)?;
Ok(contents)
}
fn parse_number(text: &str) -> Result<u32, std::num::ParseIntError> {
text.parse::<u32>() // parse() returns a Result
}
fn main() {
println!("--- Demonstrating appropriate panic! (internal bug) ---");
// Scenario 1: Admin user (logic dictates it should always succeed)
let admin_lucky_number = get_lucky_number(true);
println!("Admin lucky number: {}", admin_lucky_number);
// Scenario 2: Non-admin user (this will panic because of the expect message)
// To see this panic, uncomment the line below.
// get_lucky_number(false);
// println!("This line will not be reached if the above panics.");
println!("\n--- Demonstrating Result for recoverable errors ---");
// Example 1: Reading a file
let file_path = "example.txt";
// Create a dummy file for demonstration
fs::write(file_path, "Hello, Rust!\\n123").expect("Failed to write example file");
match read_file_contents(file_path) {
Ok(contents) => println!("File contents: \"{}\"", contents.trim()),
Err(e) => eprintln!("Error reading file: {}", e),
}
// Try reading a non-existent file
match read_file_contents("non_existent_file.txt") {
Ok(contents) => println!("Contents of non-existent file: \"{}\"", contents),
Err(e) => eprintln!("Error reading non-existent file: {}", e), // This will print an error
}
// Example 2: Parsing a number
let number_str_ok = "42";
match parse_number(number_str_ok) {
Ok(num) => println!("Parsed number: {}", num),
Err(e) => eprintln!("Error parsing '{}': {}", number_str_ok, e),
}
let number_str_err = "hello";
match parse_number(number_str_err) {
Ok(num) => println!("Parsed number: {}", num),
Err(e) => eprintln!("Error parsing '{}': {}", number_str_err, e), // This will print an error
}
// Clean up the dummy file
fs::remove_file(file_path).expect("Failed to remove example file");
println!("\n--- End of demonstration ---");
println!("Note: Uncomment 'get_lucky_number(false);' to observe a panic!");
}
```








To panic! or Not to panic!