In this lesson, you will learn how to enforce logical invariants at compile-time to prevent the generation of invalid or nonsensical code before the compiler even attempts to process the output.
The Problem of Silent Failure
When writing procedural macros, it is easy to generate code that is syntactically valid but logically broken. For example, if you are building an Entity! macro that requires a health value, the macro might accept any integer. However, if your domain logic mandates that health must be a positive, non-zero value, a standard macro would happily generate code that fails at runtime or causes weird bugs later in the execution flow.
Enforcing Invariants at Compile-Time
To "handle invariants," we perform a validation step immediately after parsing the input TokenStream but before generating the final code. By using syn and quote, we can inspect the data structures and call compile_error! if they violate our rules.
Consider a macro that generates a state machine where a max_retries field must be less than 10:
#[proc_macro]
pub fn define_retry_policy(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as PolicyConfig);
// Enforcing the invariant
if input.max_retries >= 10 {
return quote! {
compile_error!("max_retries must be less than 10 to prevent infinite loops.");
}.into();
}
// If invariant passes, proceed to generation
generate_policy(input)
}
Why This Matters
By utilizing compile_error!, we shift the burden of validation from the user's runtime to our macro's "compile-time." This turns a silent logical failure into a hard compiler error. The user receives a clear, actionable message exactly where the error occurred in their source code, rather than debugging a mysterious state machine crash three layers deep in your generated code.
This pattern is the bedrock of robust macro design: treat the input as untrusted data that must satisfy your domain's logical "contract" before you allow the compiler to build it.
Key Takeaway: Handling macro invariants means treating macro inputs as untrusted data and using compile_error! to enforce domain-specific logic before generating your output tokens.