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
andend
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 rhaiPosition
and source text into aSpan
by calculating byte offsets. -
Span::from_rhai_start_end_pos(script, start, end)
Creates a span from two RhaiPosition
. -
Span::from_rhai_span(script, rhai_span, pos)
Converts a RhaiSpan
intorhai_trace
’sSpan
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 newSpanTracer
instance. -
SpanTracer::extract_from(script)
Analyzes the provided script and returns aVec<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);
}
}
}
}