id: safety-11 original_id: G.UNS.SAS.02 level: G impact: MEDIUM clippy: 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
你可能感兴趣的文章
Do Not Expose Raw Pointers in Public APIs
Unsafe Code Authors Must Verify Safety Invariants
Ensure Consistent Data Layout for Custom Types
Add Safety Section in Docs for Public Unsafe Functions
Do Not Use Union Variants Across Different Lifetimes
Use MaybeUninit
Ensure C-ABI Compatibility for Strings Between Rust and C
Do Not Dereference Pointers Cast to Misaligned Types
Use Dedicated Opaque Type Pointers Instead of c_void for C Opaque Types