15.7 Best Practices

Effective error handling requires more than simply using Result or calling panic!. Below are general guidelines for writing robust, maintainable Rust code.

15.7.1 Return Errors to the Call Site

Whenever possible, return errors up the call stack. Let the caller decide whether to retry, log, or proceed with a fallback:

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.7.2 Meaningful Error Messages

When transforming or returning errors, add context that helps diagnose issues:

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

15.7.3 Use unwrap and expect Sparingly

The methods unwrap and expect are convenient for quick prototypes or tests but can lead to panics if something goes wrong. In production code, prefer explicit error handling unless you are absolutely sure a failure is impossible:

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