Introduction

Welcome to rhai_trace, a simple library for better Rhai errors.

Rhai has bad errors because it is designed to be a general purpose library and most of the time, good errors are not needed. But in some certain cases, some poeple may require advanced errors and that is where rhai_trace comes in.

It provides a BetterError structure which contains important fields like start and end which is very important for diagnostic like errors.

Span Structure

The Span structure is a core component of rhai_trace, representing a contiguous segment of source code with both byte-level precision and human-readable positioning. It’s essential for error reporting, diagnostics, and integration with external tools like ariadne for visual feedback.

Key Features

  • Byte Offsets: start and end mark the exact location in the source text.
  • Line & Column: Provides 1-based human-readable context for developers.
  • Use Cases: Highlights errors, annotates code, and feeds structured information into debugging or visualization tools.

Example Usage

use rhai_trace::Span;

let span = Span::new(10, 20, 2, 5);
println!(
    "Span covers bytes {}..{} on line {}",
    span.start(),
    span.end(),
    span.line()
);

Constructors & Methods

  • Span::new(start, end, line, column) Creates a span from byte offsets, line, and column information.

  • span.start() / span.end() Returns the starting and ending byte offsets.

  • span.line() / span.column() Returns the line and column numbers (1-based).

  • Span::from_pos(script, pos) Converts a rhai Position and source text into a Span by calculating byte offsets.

  • Span::from_rhai_start_end_pos(script, start, end) Creates a span from two Rhai Position.

  • Span::from_rhai_span(script, rhai_span, pos) Converts a Rhai Span into rhai_trace’s Span using contextual information.

Why It is important

The Span structure bridges machine-level parsing and human-centric debugging.

Extracting Spans

The SpanTracer is a powerful utility in rhai_trace that extracts spans from Rhai scripts, mapping every statement or expression to its exact location in the source code. It provides byte offsets, line numbers, and column positions, enabling precise diagnostics, navigation, and analysis.

Purpose

  • Parses scripts to identify code segments
  • Associates syntax elements with their location in the source text
  • Facilitates error highlighting, debugging tools, and developer feedback mechanisms

Example Usage

use rhai_trace::{SpanTracer, Span};

let code = r#"
    let a = 1;
    let b = a + 2;
"#;

let tracer = SpanTracer::new();
let spans = tracer.extract_from(code).unwrap();

for span in spans {
    println!("Span: {}..{} (line {}, column {})",
             span.start(), span.end(), span.line(), span.column());
}

This outputs each segment’s location, allowing tools to provide targeted information based on where the error or expression occurs.

Methods

  • SpanTracer::new() Creates a new SpanTracer instance.

  • SpanTracer::extract_from(script) Analyzes the provided script and returns a Vec<Span> containing all identified spans. Fails with an error if the script cannot be parsed.

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);
            }
        }
    }
}