rust.panic_in_library
Stability
High
Causes Production Outages
Detects panic!(), todo!(), unimplemented!() in library code.
Why It Matters
Section titled “Why It Matters”Libraries should return errors, not panic:
- Crashes the caller — Panics propagate to whoever uses your library
- No recovery — Callers can’t handle or retry on panic
- Trust violation — Users expect libraries to be safe
- Debugging difficulty — Panic traces are hard to understand
Application code can choose to panic. Libraries should not make that choice.
Example
Section titled “Example”// ❌ Before (library code)pub fn parse_config(s: &str) -> Config { if s.is_empty() { panic!("empty config string"); } // ...}
pub fn calculate(x: i32) -> i32 { todo!("implement later")}// ✅ Afterpub fn parse_config(s: &str) -> Result<Config, ConfigError> { if s.is_empty() { return Err(ConfigError::Empty); } // ...}
pub fn calculate(x: i32) -> Result<i32, Error> { Err(Error::NotImplemented("calculate"))}What Unfault Detects
Section titled “What Unfault Detects”panic!()in non-test, non-main codetodo!()in published codeunimplemented!()macrosunreachable!()that might be reachable.expect()in libraries (less strict)
Auto-Fix
Section titled “Auto-Fix”Unfault can convert panic patterns to Result returns with appropriate error types when the function signature allows.
When Panic Is Acceptable
Section titled “When Panic Is Acceptable”// Tests - expected behavior#[test]fn test_invalid_input() { let result = std::panic::catch_unwind(|| { dangerous_function() }); assert!(result.is_err());}
// Logic errors that indicate bugsfn index(&self, idx: usize) -> &T { // Panic on out-of-bounds is documented contract &self.data[idx]}
// Main function initializationfn main() { let config = Config::load().expect("failed to load config");}Best Practices
Section titled “Best Practices”// Use thiserror for library errors#[derive(Debug, thiserror::Error)]pub enum MyLibError { #[error("invalid input: {0}")] InvalidInput(String), #[error("not implemented: {0}")] NotImplemented(&'static str),}
// Document panic conditions if they exist/// # Panics////// Panics if `divisor` is zero.pub fn divide(a: i32, divisor: i32) -> i32 { a / divisor}