debug_assert!
The debug_assert!, debug_assert_eq!, and debug_assert_ne! macros check invariants only when cfg!(debug_assertions) is enabled (the default in cargo build and cargo test), compiling down to an empty block {} with zero runtime cost in optimized release builds (cargo build --release).
The debug_assert!* Family
| Debug-Only Macro | Always-Active Counterpart | Evaluated in cargo build (Dev)? |
Evaluated in cargo build --release? |
|---|---|---|---|
debug_assert!(cond, ...) |
assert!(cond, ...) |
Yes | No (unless debug-assertions = true in Cargo.toml) |
debug_assert_eq!(a, b, ...) |
assert_eq!(a, b, ...) |
Yes | No |
debug_assert_ne!(a, b, ...) |
assert_ne!(a, b, ...) |
Yes | No |
How debug_assert! Expands
Under the hood, debug_assert!(cond) expands to an if cfg!(debug_assertions) check:
if cfg!(debug_assertions) {
assert!(cond);
}
Because if cfg!(debug_assertions) is still parsed and type-checked by the compiler even in --release mode, variables and helper functions referenced inside debug_assert! never go stale or break compilation when switching between debug and release profiles, yet the dead branch is completely stripped before code generation.
Checking Expensive $O(n)$ Algorithm Invariants Without Slowing Release Builds
Use debug_assert! when verifying a precondition would change the asymptotic complexity of a hot function—such as checking that an input slice is sorted ($O(n)$) inside an $O(\log n)$ binary search helper:
fn lookup_in_sorted(sorted_slice: &[i32], target: i32) -> Option<usize> {
// O(n) check runs in debug/test builds to catch caller bugs,
// but costs 0 instructions in `--release` so the function stays O(log n):
debug_assert!(
sorted_slice.is_sorted(),
"lookup_in_sorted requires an ascending sorted slice, got {sorted_slice:?}"
);
sorted_slice.binary_search(&target).ok()
}
fn main() {
let data = [10, 20, 30, 40, 50];
println!("Index of 30: {:?}", lookup_in_sorted(&data, 30));
}
Index of 30: Some(2)
Critical Rule: Never Put Required Side Effects Inside debug_assert!
Because the expression inside debug_assert!(expr) is not evaluated at all in release builds, never place state-mutating calls (iter.next(), vec.pop(), counter += 1, or I/O operations) inside debug_assert!. Only use assert! when a check guards memory safety (unsafe preconditions) or must execute in production.