Rust LogoTo panic! or Not to panic!

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!");
}
```