Skip to content
rusthacks.dev
Testing

Assertions in Rust Assert Eq Matches and Custom Messages

assert, assert_eq, assert_ne, matches and debug_assert, with custom failure messages, float comparison, and what each prints. Includes a helper for readable domain assertions.

3 min read
Summarize in AI
Flat illustration for the post “Assertions in Rust Assert Eq Matches and Custom Messages”, composed around testing.

A failing assertion should tell you what went wrong without opening the debugger. Rust's assertion macros help by printing values, and a custom message supplies the rest. The common mistakes are asserting a boolean where assert_eq! would show the values, and comparing floats exactly. This post covers the macros and a small helper for domain checks. It is part of the testing in Rust guide, and goes with unit tests.

Rust 1.90, edition 2024.

The macros

rust
#[derive(Debug, PartialEq)]
enum State { Idle, Running { pid: u32 } }

#[test]
fn basics() {
    let items = vec![1, 2, 3];
    assert!(!items.is_empty(), "list should not be empty");
    assert_eq!(items.len(), 3);
    assert_ne!(items[0], items[1]);

    let s = State::Running { pid: 42 };
    assert!(matches!(s, State::Running { pid } if pid > 0));
}

All four pass. Prefer assert_eq!(items.len(), 3) over assert!(items.len() == 3): on failure the first prints both numbers, and the second says only that the condition was false.

What a failure prints

rust
#[test]
fn shows_both_sides() {
    let total = 2 + 2;
    assert_eq!(total, 5, "arithmetic broke for input {}", 2);
}
Text
thread 'shows_both_sides' panicked at src/lib.rs:4:5:
assertion `left == right` failed: arithmetic broke for input 2
  left: 4
 right: 5

Order is conventional: the value under test on the left, the expected value on the right. The message after the two values is formatted like println!. Use it to state the cause when the values alone do not say what they represent.

Enums and errors

assert_eq!(result, Err(MyError::NotFound)) requires PartialEq and Debug on the error type. Many error types hold std::io::Error and cannot derive PartialEq. Then use matches!:

rust
assert!(matches!(parse("x"), Err(ParseError::BadDigit { .. })));

A failure of matches! prints only "assertion failed". For better output, let err = parse("x").unwrap_err(); assert!(matches!(err, ParseError::BadDigit { .. }), "got {err:?}");.

Floats

rust
fn close(a: f64, b: f64) -> bool {
    (a - b).abs() < 1e-9
}

#[test]
fn sums_are_close() {
    let sum: f64 = [0.1, 0.2, 0.3].iter().sum();
    assert!(close(sum, 0.6), "sum was {sum}");
    assert_ne!(sum, 0.6);
}

The exact comparison fails, since 0.1 + 0.2 + 0.3 is 0.6000000000000001 in binary floating point. The approx crate provides assert_relative_eq! with relative and absolute tolerances, which is better than a hand-picked epsilon when magnitudes vary.

A helper for domain checks

rust
fn assert_sorted<T: PartialOrd + std::fmt::Debug>(v: &[T]) {
    for pair in v.windows(2) {
        assert!(pair[0] <= pair[1], "not sorted at {:?} then {:?} in {:?}", pair[0], pair[1], v);
    }
}

A named helper turns a repeated check into a one-line assertion with a message that says what was violated. Put #[track_caller] on it so a failure points to the calling test's line and not to the helper.

debug_assert

debug_assert! checks internal invariants in your library code during tests and development, and costs nothing in release. Do not use it to validate input.

Limits

Assertions panic, so the first failure stops the test, and later checks do not run. Collect several results and assert once if you need to see them all. For large structured values, a snapshot is clearer: see snapshot tests with insta. The macros are documented in the standard library for assert_eq.

Taggedassert
Share

Keep reading

Get new posts by email

We send one email when a new post is published. No spam.

Unsubscribe in one click. No spam, ever.