assert-struct LLM Reference
This file is structured for LLM consumption: dense, exhaustive, no motivational prose.
MACRO SIGNATURE
assert_struct!(expr, pattern);
expr is any Rust expression. pattern is one of the forms described below.
Panics on mismatch with a formatted error showing field path and pattern location.
WHEN TO USE assert_struct!
Use whichever form produces fewer characters. assert_struct! wins when asserting
multiple fields or using patterns not available in assert_eq!. assert_eq! wins
when a constructor or single field access is shorter.
// assert_eq! wins — constructor is shorter than enumerating fields
assert_eq!(result, Foo::default());
// assert_struct! wins — one call vs multiple assert_eq! calls
assert_struct!(foo, _ { x: 1, y: 2 });
// vs.
assert_eq!(foo.x, 1); assert_eq!(foo.y, 2);
// assert_eq! wins — single field
assert_eq!(foo.x, 1);
STRATEGY: WRITE THE MOST SUCCINCT PATTERN
Don't write the full structural pattern when an operator expression is shorter.
Operator patterns (==, !=, <, >, =~) go through PartialEq, PartialOrd, and Like,
so they work whenever the field type implements those traits for the RHS type.
// DON'T — verbose structural pattern
field: Value::Int(42)
field: Value::Text("alice") // string literal works here, but still verbose
field: Status::Code(200)
// DO — operator pattern is shorter and requires no imports
field: == 42i64
field: == "alice"
field: == 200
Note: method calls (.to_string(), etc.) are NOT valid in pattern position (no leading
operator). Only plain values, literals, and nested patterns are valid there. Method
calls are only valid on the RHS of an operator: field: == some_fn().
Mix structural and operator patterns freely within one call — structural for
navigating nested types, operators for the leaf values:
// DON'T
assert_struct!(q, Query {
filter: Some(_ { value: Value::Text("alice"), .. }),
});
// DO
assert_struct!(q, Query {
filter: Some(_ { value: == "alice", .. }),
});
PATTERN GRAMMAR (informal EBNF)
pattern ::= struct_pat | enum_pat | slice_pat | set_pat | map_pat
| tuple_pat | comparison | range | equality | regex
| method_call | index_op | deref | closure | wildcard | expr
struct_pat ::= (TypePath | "_") "{" field_assertion* ".."? "}"
field_assertion ::= field_lhs ":" pattern ","
field_lhs ::= IDENT ("." IDENT)*
| "*"+ IDENT
| IDENT ("[" expr "]")+ ("." IDENT)?
| IDENT "." IDENT "(" args? ")"
| IDENT "[" expr "]" "." IDENT "(" args? ")"
enum_pat ::= path (unit variant)
| path "(" pattern ("," pattern)* ")" (tuple variant)
| path "{" field_assertion* ".."? "}" (struct variant)
slice_pat ::= "[" (pattern ",")* ".."? (pattern ",")* "]"
set_pat ::= "#(" (pattern ",")* ".."? ")"
map_pat ::= "#{" (string_lit ":" pattern ",")* ".."? "}"
tuple_pat ::= "(" (pattern | index_method) ("," (pattern | index_method))* ")"
index_method ::= INT_LIT "." IDENT "(" args? ")" ":" pattern
comparison ::= (">" | ">=" | "<" | "<=") expr
equality ::= ("==" | "!=") expr
range ::= expr? ".." "="? expr? (Rust range syntax)
regex ::= "=~" (raw_string_lit | expr) (raw str = compile-time; expr = Like trait)
deref ::= "*"+ field_lhs (field assertions only)
closure ::= ("|" IDENT "|" | "move" "|" IDENT "|") bool_expr
wildcard ::= "_"
STRUCT PATTERNS
Named struct — partial matching requires ".."
assert_struct!(value, MyType {
field_a: 42,
field_b: "hello",
.. // required for partial; omit only if asserting ALL fields
});
Wildcard struct — avoids type import; ".." is optional, matching is always partial
assert_struct!(value, _ {
field_a: 42,
field_b: > 0,
});
Nested structs
assert_struct!(value, Outer {
inner: Inner {
x: 1,
..
},
other: _ {
y: 2,
},
..
});
Nested field access shorthand (dot-path as field LHS)
assert_struct!(value, MyType {
user.profile.age: >= 18,
user.profile.name: "alice",
..
});
// Equivalent to nesting User { Profile { age: >= 18, .. }, .. }
COMPARISON PATTERNS
field: > 10
field: >= 10
field: < 100
field: <= 100
RHS is any expression. Requires PartialOrd. Works inside any compound pattern.
EQUALITY PATTERNS
field: == 42 // explicit equality (PartialEq)
field: != 0 // not-equal
Plain "field: 42" also asserts equality (implicit ==).
RANGE PATTERNS
Rust native range syntax. Requires PartialOrd on value type.
field: 18..=65 // inclusive both ends
field: 0..100 // exclusive upper
field: 18.. // no upper bound
field: ..100 // no lower bound (exclusive)
field: ..=100 // no lower bound (inclusive)
field: .. // any value
field: 'A'..='Z' // char range
field: 0.0..100.0 // float range
REGEX / LIKE PATTERNS
// Requires regex feature (on by default). Compiled at macro expansion time.
field: =~ r"^\d{4}-\d{2}-\d{2}$"
// Any expression implementing Like<T>. Works without regex feature. Runtime evaluation.
field: =~ my_pattern_var
field: =~ build_pattern()
NON-OBVIOUS: "=~ r\"...\"" (raw string literal) and "=~ expr" (any expression) are two
separate code paths. The raw string compiles the regex at macro time (zero runtime cost);
expressions use the Like trait at runtime.
ENUM PATTERNS
Option
field: None
field: Some(42)
field: Some(> 30)
field: Some("text") // string literal auto-coerced, no .to_string()
field: Some([1, 2, 3])
field: Some(Inner { x: 1, .. })
Result
field: Ok(42)
field: Ok(> 0)
field: Err("message")
field: Err(ErrorType { code: 500, .. })
Custom enums — unit variant
field: Status::Active
field: Status::Pending
Custom enums — tuple variant
field: Event::Click(x, y)
field: Event::Click(>= 0, < 1920)
field: Message::Data("meta", [> 0, < 10])
Custom enums — struct variant
field: Status::Error { code: 500, .. }
field: Status::Error { code: > 400, message: =~ r"timeout" }
Nested enums
field: Outer::Wrap(Inner::A(42))
field: Some(Status::Active)
field: Ok(Some(> 0))
NON-OBVIOUS: Enum patterns generate match expressions (not let bindings), so they are
exhaustive — any unmatched variant panics with a useful message.
SLICE / VEC PATTERNS (position-based, length-checked)
field: [] // exactly empty
field: [1, 2, 3] // exactly three elements
field: [> 0, < 10, == 5] // per-element patterns
field: [1, 2, ..] // first two elements, rest ignored
field: [.., 4, 5] // last two elements
field: [1, .., 5] // first and last
field: [..] // any slice (length not checked)
field: ["a", "b"] // string literals
field: [Some(1), None, Some(> 0)] // enums inside slices
field: [[1, 2], [3, 4]] // nested slices
Constraint: at most one ".." per slice pattern. Length is enforced when ".." is absent.
SET PATTERNS (unordered, backtracking)
field: #() // exactly empty
field: #(1, 2, 3) // exactly these elements, any order
field: #(> 0, < 10, >= 50) // per-element patterns, any order
field: #(1, 2, ..) // contains at least 1 and 2 (any order)
field: #(..) // any collection
field: #(None, Some(> 0), ..)
field: #(_ { kind: "click", .. }, _ { kind: "hover", .. }, ..)
NON-OBVIOUS: Uses backtracking to match elements. Each pattern is tried against remaining
unmatched elements. With "..", extra elements are allowed.
MAP PATTERNS (duck-typed: needs len() and get())
field: #{} // exactly empty
field: #{ "k": "v" } // exact match (length enforced)
field: #{ "k": "v", .. } // partial match (extra keys allowed)
field: #{ "count": > 40, "score": >= 90, .. }
field: #{ "email": =~ r".*@.*\.com", .. }
field: #{ "item": NestedType { x: 1, .. }, .. }
Keys must be string literals. Values accept any pattern.
TUPLE PATTERNS
field: (1, 2)
field: (> 10, < 30)
field: ("alice", 30, true)
field: (1, _, 3) // _ ignores element
field: (Some(1), None)
Tuple index method calls (inside tuple patterns):
Syntax — INT_LITERAL "." method_name "(" args? ")" ":" pattern
field: (0.len(): 5, 1.len(): > 3)
field: (0.starts_with("pre"): true, _)
field: (0.contains(&5): true, 1.is_some(): true)
INT_LITERAL is the zero-based tuple index.
METHOD CALL PATTERNS (as field assertions)
field.len(): 10
field.len(): > 5
field.is_empty(): false
field.contains("word"): true
field.get("key"): Some("value")
field.starts_with("pre"): true
Syntax: IDENT "." IDENT "(" args? ")" ":" pattern as a field-level assertion.
INDEX OPERATIONS (as field assertions)
field[0]: 42
field[0]: > 5
field[i]: pattern // variable index
values[0][1]: 99 // nested indexing
items[0].name: "alice" // index then field access
names[0].len(): 5 // index then method call
DEREFERENCE PATTERNS (smart pointers: Box, Rc, Arc)
*boxed: 42
*rc_str: "hello"
*arc_val: > 50
**double_box: true
Multiple "*" for multiple dereference levels.
CLOSURE PATTERNS
field: |x| x > 5
field: |x| x.len() > 0 && x.starts_with("prefix")
field: move |x| x > threshold
field: |x| { let n = x * 2; n < 100 }
Closure takes one parameter of the field's type and returns bool.
true = pass, false = fail.
WILDCARD PATTERN
field: _ // field must exist but any value passes
In tuples: (1, _, 3) — ignores middle element.
In slices: [1, _, 3] — ignores middle element.
STRING LITERAL AUTO-COERCION
String literals are automatically compared without .to_string() in all contexts:
field: "hello"
field: Some("text")
field: Ok("message")
field: ["a", "b", "c"]
field: #("x", "y")
field: #{ "key": "value" }
field: ("hello", "world")
Works with both String and &str field types.
NOT SUPPORTED — INVALID SYNTAX
// Method chaining on single field
field.method1().method2(): pattern // INVALID
// Nested field path with method call
outer.inner.method(): pattern // INVALID
// Multiple ".." in same struct
Type { .., field: x, .. } // INVALID — compile error
// Multiple ".." in same slice
[1, .., 3, ..] // INVALID
// "=~ r\"...\"" without regex feature — compile error
// Use "=~ expression" with Like trait instead
FEATURE FLAGS
Feature Default Effect
regex enabled Enables "=~ r\"...\"" raw string regex syntax.
Without it, only "=~ expr" (Like trait) works.
// Cargo.toml
assert-struct = "0.3" // regex on
assert-struct = { version = "0.3", default-features = false } // no regex
THE LIKE TRAIT
Like<T> is the trait powering "=~ expr". Implement for custom matchers.
use assert_struct::Like;
struct MyMatcher;
impl Like<String> for MyMatcher {
fn like(&self, other: &String) -> bool {
other.starts_with("expected-prefix")
}
}
assert_struct!(data, MyType {
field: =~ MyMatcher,
..
});
Built-in implementations: String and &str implement Like<&str> and Like<String>
(pattern interpreted as regex); String and &str implement Like<regex::Regex>
(pre-compiled regex). All built-in implementations require the regex feature.
FULL COMPOSITION EXAMPLE
assert_struct!(response, Response {
// equality (implicit ==)
status: 200,
// comparison
latency_ms: < 500,
// explicit equality / inequality
retry_count: == 0,
error_code: != 404,
// range
score: 0..=100,
// regex (regex feature)
request_id: =~ r"^req-[a-f0-9]{8}$",
// nested struct
user: User {
id: > 0,
name: "alice",
age: >= 18,
active: true,
..
},
// wildcard struct (no import needed)
config: _ {
timeout: > 0,
retries: 3,
},
// nested field shorthand
user.profile.city: "SF",
// Option / Result
cached_value: Some(> 0),
api_result: Ok("success"),
// custom enum
event: Event::Click(>= 0, < 1920),
state: Status::Active,
// slice (position-based)
top_scores: [> 90, > 85, > 80],
// slice with rest
tags: ["important", ..],
// set (unordered)
permissions: #("read", "write", ..),
// map
headers: #{ "content-type": =~ r"application/json", .. },
// tuple
dimensions: (> 0, > 0),
// method call
items.len(): > 0,
// index
items[0]: > 0,
// dereference
*boxed_count: >= 1,
// closure
raw_value: |v| v % 2 == 0,
// wildcard
internal_id: _,
..
});