mirror of
https://github.com/leonardomso/rust-skills.git
synced 2026-09-14 19:33:21 +08:00
0016d5cfb2
Includes rules for: - Ownership and borrowing patterns - Error handling with thiserror/anyhow - Memory management and allocation - API design following Rust guidelines - Async/Tokio patterns - Performance optimization - Naming conventions - Type safety - Testing strategies - Documentation standards - Project structure - Linting configuration - Common anti-patterns to avoid
3.7 KiB
3.7 KiB
err-expect-bugs-only
Use
expect()only for invariants that indicate bugs, not user errors
Why It Matters
expect() is better than unwrap() because it provides context, but it still panics. Reserve it for situations where failure indicates a bug in your code—a violated invariant, not a user error or external failure. The message should explain why the invariant should hold, helping future developers understand and fix the bug.
Bad
// User input can legitimately fail - don't expect
fn parse_user_input(input: &str) -> Config {
serde_json::from_str(input)
.expect("Invalid JSON") // User error, not a bug!
}
// Network can fail - don't expect
fn fetch_data(url: &str) -> Data {
reqwest::get(url)
.expect("Network request failed") // External failure!
.json()
.expect("Invalid response")
}
// File might not exist - don't expect
fn load_config() -> Config {
let content = fs::read_to_string("config.json")
.expect("Config file missing"); // Environment issue!
}
Good
// Invariant: after insert, key exists
fn cache_and_get(&mut self, key: String, value: Value) -> &Value {
self.cache.insert(key.clone(), value);
self.cache.get(&key)
.expect("BUG: key must exist immediately after insert")
}
// Invariant: regex is compile-time constant
fn create_parser() -> Regex {
Regex::new(r"^\d{4}-\d{2}-\d{2}$")
.expect("BUG: date regex is invalid - this is a compile-time constant")
}
// Invariant: already validated
fn process_validated(data: ValidatedData) -> Result<Output, ProcessError> {
let value = data.required_field
.expect("BUG: ValidatedData guarantees required_field is Some");
// ...
}
// Invariant: type system guarantees
fn get_first<T>(vec: Vec<T>) -> T
where
Vec<T>: NonEmpty, // Hypothetical trait
{
vec.into_iter().next()
.expect("BUG: NonEmpty Vec cannot be empty")
}
expect() Message Guidelines
Messages should:
- Start with "BUG:" or similar to indicate it's an invariant
- Explain WHY the invariant should hold
- Help developers fix the issue
// ❌ Bad messages
.expect("failed") // No context
.expect("should not be None") // Doesn't explain why
.expect("Invalid state") // Vague
// ✅ Good messages
.expect("BUG: HashMap entry exists after insert")
.expect("BUG: validated input must parse - validation is broken")
.expect("BUG: static regex compilation failed - regex syntax error in source")
Pattern: Validate Once, expect() After
struct ValidatedEmail(String);
impl ValidatedEmail {
pub fn new(email: &str) -> Result<Self, EmailError> {
// Validation happens here, returns Result
if !is_valid_email(email) {
return Err(EmailError::Invalid);
}
Ok(ValidatedEmail(email.to_string()))
}
pub fn domain(&self) -> &str {
// After validation, expect() is fine
self.0.split('@').nth(1)
.expect("BUG: ValidatedEmail must contain @")
}
}
Alternatives When expect() Is Wrong
// Don't: expect on user data
let port: u16 = input.parse().expect("Invalid port");
// Do: Return Result
let port: u16 = input.parse().map_err(|_| ConfigError::InvalidPort)?;
// Do: Provide default
let port: u16 = input.parse().unwrap_or(8080);
// Do: Handle explicitly
let port: u16 = match input.parse() {
Ok(p) => p,
Err(_) => {
log::warn!("Invalid port '{}', using default", input);
8080
}
};
See Also
- err-no-unwrap-prod - Avoiding unwrap in production
- err-result-over-panic - When to return Result
- api-parse-dont-validate - Type-driven validation