SNAFU Virtual Stack Trace
A lightweight, efficient error handling library for Rust that implements virtual stack traces based on GreptimeDB's error handling approach. This library combines the power of SNAFU error handling with virtual stack traces to provide meaningful error context without the overhead of system backtraces.
Table of Contents
Motivation
Traditional error handling in Rust often faces a dilemma:
- Option 1: Use system backtraces - long hard to read stack traces only referencing functions and lines
- Option 2: Simple error propagation - lacks context about where errors originated
Virtual stack traces provide a third way: capturing meaningful context at each error propagation point with minimal overhead.
Features
- 🚀 Lightweight: Only ~100KB binary overhead vs several MB for system backtraces
- 📍 Precise Location Tracking: Automatically captures file, line, and column information
- 🔗 Error Chain Walking: Traverses the entire error source chain
- 🎯 Zero-Cost Abstraction: Context generation can be postponed until needed
- 🛠️ Seamless Integration: Works perfectly with SNAFU error handling
- 📝 Developer-Friendly: Automatic Debug implementation with formatted stack traces
Installation
Add these dependencies to your Cargo.toml:
[]
= "0.8"
= "0.1"
Usage
Benefits and Motivations
The virtual stack trace approach provides several key advantages:
1. Performance Efficiency
Unlike system backtraces that capture the entire call stack (expensive operation), virtual stack traces only record error propagation points. This results in:
- Lower CPU usage during error handling
- Reduced memory footprint
- Smaller binary sizes (100KB vs several MB)
2. Meaningful Context
Virtual stack traces capture:
- The exact location where each error was propagated
- Custom error messages at each level
- The complete error chain from root cause to final error
3. Production-Ready
- Safe to use in production environments
- No performance penalties in the happy path
- Can be enabled/disabled at runtime if needed
4. Developer Experience
- Clear, readable error messages
- Easy to debug error propagation paths
- Automatic integration with existing SNAFU errors
5. Flexible Error Presentation
- Detailed traces for developers during debugging
- Simplified messages for end users
- Customizable formatting for different contexts
Basic Example
use *;
use stack_trace_debug;
// When an error occurs, you get a detailed virtual stack trace:
// Error: Failed to read configuration file
// Virtual Stack Trace:
// 0: Failed to read configuration file at src/main.rs:45:10
// 1: No such file or directory (os error 2) at src/main.rs:46:15
Advanced Example
use *;
use ;
// Error handling in practice
async
Boxing Errors
When working with errors that need to be boxed (e.g., when using Box<dyn std::error::Error + Send + Sync>), you can use the .boxed() method to convert errors before applying context:
use *;
use stack_trace_debug;
The .boxed() method is particularly useful when:
- You need to erase the specific error type
- Working with trait objects that require
Send + Sync - Dealing with multiple error types that need to be unified
- Creating library APIs that don't want to expose implementation details
Do's and Don'ts
✅ Do's
-
DO use
#[stack_trace_debug]on all error enums// Always add this -
DO provide meaningful error messages
ProcessingFailed , -
DO use context when propagating errors
operation .context?; -
DO separate internal and external errors
// Internal error with full details // External error for API responses -
DO leverage the error chain
// Each level adds context low_level_op .context? .middle_layer .context? .high_level .context?; -
DO use
ensure!for validationensure!;
❌ Don'ts
-
DON'T use system backtraces in production
// Avoid this in production set_var; -
DON'T ignore error context
// Bad: loses context operation.map_err?; // Good: preserves context operation.context?; -
DON'T create deeply nested error types
// Bad: too many levels // Good: flat structure with sources -
DON'T expose internal errors directly to users
// Bad: exposes implementation details return Err; // Good: map to user-friendly error return Err; -
DON'T forget to handle all error variants
// Use exhaustive matching match error -
DON'T mix error handling strategies
// Pick one approach and stick to it // Either use SNAFU throughout or another error library // Don't mix multiple error handling crates
How It Works
-
Proc Macro Magic: The
#[stack_trace_debug]attribute automatically implements:VirtualStackTracetrait for stack frame collection- Custom
Debugimplementation for formatted output
-
Location Tracking: Uses Rust's
#[track_caller]to capture precise locations where errors are propagated -
Error Chain Walking: Automatically traverses the
source()chain to build complete error context -
Zero-Cost Until Needed: Stack frames are only generated when the error is actually inspected
API Reference
Core Traits
VirtualStackTrace
StackFrame
Attributes
#[stack_trace_debug]
Attribute macro that automatically implements virtual stack trace functionality for SNAFU error enums.
Contributing
Contributions are welcome! Please feel free to submit issues and pull requests.
License
This project is licensed under the Apache 2.0 License - see the LICENSE file for details.
Acknowledgments
- Heavily inspired by GreptimeDB's error handling approach
- Built on top of the excellent SNAFU error library
- Thanks to the Rust community for amazing error handling discussions