Skip to main content

assert_matches

Macro assert_matches 

Source
macro_rules! assert_matches {
    ($got:expr, $want:pat $(,)?) => { ... };
}
Expand description

Checks whether $got matches $want.

$got is an arbitrary expression, it is evaluated unchanged. Must implement std::fmt::Debug. $want is an expression resembling a pattern syntax.

§Motivation

The primary goal of this macro over a more standard assert_matches! syntax is to allow flexible checks of nested fields for types that don’t implement PartialEq, e.g. various Error types.

Example:

#[derive(Debug)]
enum Error {
    A,
    B(String),
    C(Box<Error>),
}

fn my_func() -> Result<(), Error> {
    Err(Error::C(Box::new(Error::B("failed".to_owned()))))
}

assert_matches!(my_func(), Err(Error::C(deref!(Error::B("failed")))));

As of Rust 1.95 this is not possible to express in a single check without unstable deref_patterns. The next best thing is something along these lines:

#[derive(Debug)]
enum Error {
    A,
    B(String),
    C(Box<Error>),
}

fn my_func() -> Result<(), Error> {
    Err(Error::C(Box::new(Error::B("failed".to_owned()))))
}

let r = my_func();
let Err(Error::C(err)) = my_func() else {
    panic!("Expected Error::C, got {r:?}");
};
let Error::B(msg) = *err else {
    panic!("Expected Error::B, got {err:?}");
};
assert_eq!(msg, "failed");

In author’s opinion the alternative is less expressive.

§$want syntax

$want generally follows Rust pattern syntax, except the ability to bind data. Every field must be either checked against a matcher expression, explicitly ignored via matching to _, or omitted entirely.

§Binding issues

Due to how procedural macros are implemented it is impossible to know whether a specific path (e.g. Err, std::option::Option::Some, or even x) is a variable name or a type name. This macro will always assume a type name. This can lead to surprising compilation errors if a variable is provided instead.

For example the following may result in a compilation error because the value is unused.

#[derive(Debug)]
struct Type(&'static str);

#[deny(unused)]
fn main() {
    let x = Type("hello");
    assert_matches!(x, Type(value));
}

§deref! matcher

Allows matching on inner types implementing Deref.

Inspired by deref_patterns.

#[derive(Debug)]
struct Inner(String);

#[derive(Debug)]
struct Outer {
    inner: Box<Inner>,
}

let x = Outer {
    inner: Box::new(Inner("hello".into())),
};
assert_matches!(x, Outer {
    inner: deref!(Inner("hello")),
});

§eq! matcher

Allows matching on variables where the syntax would otherwise be ambiguous.

#[derive(Debug)]
struct Type(i32);

let want = 10;
let got = Type(10);
assert_matches!(got, Type(eq!(want)));

§Examples

§Literals

let x = 10;
assert_matches!(x, 10);

§Boxed types

#[derive(Debug)]
enum Enum {
    A,
    B,
}

// Box is a Deref type, so it has to be unwrapped first.
#[derive(Debug)]
struct Struct(Box<Enum>);

let thing = Struct(Box::new(Enum::A));
assert_matches!(thing, Struct(deref!(Enum::A)));

§Partial struct matching

#[derive(Debug)]
struct Type {
    a: i32,
    b: i32,
}

let thing = Type { a: 1, b: 2 };
assert_matches!(thing, Type { a: 1, .. });