3.1 KiB
3.1 KiB
id, original_id, level, impact, clippy
| id | original_id | level | impact | clippy |
|---|---|---|---|---|
| safety-11 | G.UNS.SAS.02 | G | MEDIUM | debug_assert_with_mut_call |
Use assert! Instead of debug_assert! in Unsafe Functions
Summary
In unsafe functions or functions containing unsafe blocks, prefer assert! over debug_assert! for checking safety invariants.
Rationale
debug_assert! is compiled out in release builds. If an invariant is important enough to check for safety, it should be checked in all builds to catch violations.
Bad Example
// DON'T: Use debug_assert for safety-critical checks
pub unsafe fn get_unchecked(slice: &[i32], index: usize) -> &i32 {
debug_assert!(index < slice.len()); // Gone in release!
&*slice.as_ptr().add(index)
}
// DON'T: Rely on debug_assert for FFI safety
pub unsafe fn call_c_function(ptr: *const Data) {
debug_assert!(!ptr.is_null()); // Won't catch bugs in release
ffi::process_data(ptr);
}
Good Example
// DO: Use assert! for safety checks (when performance allows)
pub unsafe fn get_unchecked(slice: &[i32], index: usize) -> &i32 {
assert!(index < slice.len(), "index {} out of bounds for len {}", index, slice.len());
&*slice.as_ptr().add(index)
}
// DO: Use debug_assert when CALLER is responsible
/// # Safety
/// index must be less than slice.len()
pub unsafe fn get_unchecked_fast(slice: &[i32], index: usize) -> &i32 {
// Caller is responsible; debug_assert just helps catch bugs during development
debug_assert!(index < slice.len());
&*slice.as_ptr().add(index)
}
// DO: Use assert for internal safety, debug_assert for caller obligations
pub fn get_checked(slice: &[i32], index: usize) -> Option<&i32> {
if index < slice.len() {
// SAFETY: We just checked index < len
// debug_assert is fine here because the if-check is the real guard
Some(unsafe {
debug_assert!(index < slice.len()); // Redundant, just for documentation
&*slice.as_ptr().add(index)
})
} else {
None
}
}
When to Use Each
| Assertion | Use When |
|---|---|
assert! |
Invariant is not already checked; function is called with untrusted input |
debug_assert! |
Invariant is the caller's responsibility (documented in # Safety); performance-critical |
| No assert | Invariant is enforced by types or prior checks in the same function |
Hybrid Approach
// Use cfg to have both safety and performance
pub unsafe fn process(slice: &[u8], index: usize) {
// Always check in tests and debug
#[cfg(any(test, debug_assertions))]
assert!(index < slice.len());
// Optional: paranoid mode for production
#[cfg(feature = "paranoid")]
assert!(index < slice.len());
// SAFETY: Caller guarantees index < len (checked in debug)
let ptr = slice.as_ptr().add(index);
// ...
}
Checklist
- Is this a safety-critical invariant?
- Who is responsible for upholding it (caller or this function)?
- Can the assertion be optimized away when provably true?
- What's the performance impact of the assertion?
Related Rules
safety-02: Verify safety invariantssafety-09: SAFETY comments