Common Standard Macros

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.