Propagating Better Errors

rhai_trace enables structured, insightful error reporting by transforming raw Rhai errors into enriched diagnostics. Instead of handling opaque error messages, developers can seamlessly integrate contextualized error reports into tooling like IDEs or debuggers.

Core Concept: BetterError

At its heart is the BetterError struct, designed to aggregate error metadata:

pub struct BetterError {
    pub message: String,
    pub help: Option<String>,
    pub hint: Option<String>,
    pub note: Option<String>,
    pub span: Span,
}
  • Message: what went wrong (original error)
  • Help: actionable suggestions
  • Hint: contextual nudges
  • Note: additional insights
  • Span: location in source code

BetterError makes it possible to enhance diagnostics with code context or execution.

Improving Errors

rhai_trace offers two functions for enhancing diagnostics:

BetterError::improve_eval_error

Used for runtime errors where the script was successfully parsed and spans can be extracted.

if let Ok(better) = BetterError::improve_eval_error(
    &e,
    &code,
    &engine,
    None // <-- can replace with spans from `SpanTracker`
) {
    // Use `better` to enrich error reporting in tooling
} else {
    eprintln!("Original Error: {:?}", e);
}

If you want improve_eval_error to not extract spans every time, then you can pass the result from SpanTracer as the fourth argument.

This may be useful if you want to improve performance by caching the spans and reusing it when needed.

BetterError::improve_parse_error

Used for syntax errors where the script failed to compile and spans cannot be extracted.

let ast = engine.compile(code)
    .map_err(|e| BetterError::improve_parse_error(&e, &code, &engine));

improve_parse_error does not require spans because parsing failed before code locations could be reliably extracted.

Practical Example

use rhai_trace::*;

fn main() {
    let code = r#"
        fn multiply(x, y) { x * y }
        print(multiply("a", 7)); // triggers runtime error
    "#;

    let engine = Engine::new();

    match engine.eval::<rhai::Dynamic>(code) {
        Ok(_) => println!("Executed successfully"),
        Err(e) => {
            if let Ok(better) = BetterError::improve_eval_error(
                &e,
                code,
                &engine,
                None
            ) {
                // `better` == `BetterError`
                //
                // Each value can be extracted and
                // piped to diagnostic tools.
                plug_into_codespan(better);
            } else {
                eprintln!("Original Error: {:?}", e);
            }
        }
    }
}