15.6 Best Practices

Simply using Result or calling panic! does not suffice for robust error handling. Thoughtful application of Rust’s mechanisms will result in maintainable, clear, and safe code.

15.6.1 Return Errors to the Call Site

Whenever possible, let the caller decide how to handle an error:

fn read_config_file() -> Result<Config, io::Error> {
    let contents = std::fs::read_to_string("config.toml")?;
    parse_config(&contents)
}

fn main() {
    match read_config_file() {
        Ok(config) => apply_config(config),
        Err(e) => {
            eprintln!("Failed to read config: {}", e);
            apply_default_config();
        }
    }
}

15.6.2 Provide Clear Error Messages

When transforming errors, include context to help debug problems:

fn read_file(path: &str) -> Result<String, String> {
    std::fs::read_to_string(path)
        .map_err(|e| format!("Error reading '{}': {}", path, e))
}

15.6.3 Use unwrap and expect Sparingly

While unwrap or expect are handy during prototyping or in test examples, avoid them in production code unless you are certain an error is impossible:

let content = std::fs::read_to_string("config.toml")
    .expect("Unable to read config.toml; please check the file path!");

Overusing these methods can lead to unexpected panics at runtime, making debugging more difficult.