Course contentsShow
← Rust
Lesson 1552 of 1,91013. Metaprogramming with MacrosFree lesson

Advanced Error Recovery

You will design error-tolerant parsers that provide exhaustive feedback on malformed code inputs.

In this lesson, you will learn how to design procedural macros that continue parsing after an error, allowing you to report multiple issues in a single compilation pass rather than failing at the first hurdle.

The "Fail-Fast" Problem

By default, when a procedural macro encounters a syntax error and calls panic! or returns a Diagnostic with a hard error, the Rust compiler stops processing that macro immediately. This forces users into a frustrating "whack-a-mole" cycle: fixing one error only to have the compiler reveal the next.

Implementing Error Recovery

Advanced error recovery allows your macro to detect a malformed segment, record the diagnostic, and then skip forward to a "synchronization point"—such as a semicolon or a closing brace—to continue parsing the remainder of the input.

To achieve this, avoid panicking. Instead, accumulate errors using proc_macro::Diagnostic (or the Diagnostic API via proc-macro2's span support). When you encounter a syntax violation, emit the error but attempt to return a "partial" AST or a TokenStream that allows the parser to regain its footing.

Consider this conceptual loop for a DSL macro:

let mut errors = Vec::new();
let mut output = TokenStream::new();

while !input.is_empty() {
    match parse_item(&mut input) {
        Ok(item) => output.extend(quote!(#item)),
        Err(e) => {
            errors.push(e);
            // Synchronize: consume tokens until a logical boundary
            let _ = input.step_until_boundary(); 
        }
    }
}

if !errors.is_empty() {
    return errors.into_iter().map(|e| e.emit()).collect();
}

Strategic Synchronization

The success of your recovery depends on selecting the right synchronization tokens. If your macro parses a comma-separated list, your recovery should consume tokens until the next comma or the closing delimiter. By injecting a "placeholder" node (like Default::default() or an empty expression) into your output TokenStream when an error occurs, you ensure that the downstream compiler doesn't throw secondary, confusing errors caused by missing structural elements. This transforms your macro from a fragile gatekeeper into a helpful diagnostic tool.

Key Takeaway: Instead of aborting on the first syntax error, accumulate diagnostics and synchronize your parser to the next logical boundary to provide a comprehensive, multi-issue feedback loop to the user.