Expand description
Effortless, low overhead error tracing in Rust.
Tres provides error traces with no stack unwinding and no symbol
lookups. All you need to do is wrap your error type(s) with
Traced<E>
and make sure you use a Result
type that
supports return tracing.
Usage
Consider the following two “crates” with their respective error types:
mod crate_one {
#[derive(Debug)]
pub enum Error {
FileTooLarge {
size: u64
},
CrateTwo(crate_two::Error),
}
impl From<crate_two::Error> for Error {
fn from(error: crate_two::Error) -> Self {
Self::CrateTwo(error)
}
}
pub fn foo() -> Result<(), Error> {
let size = crate_two::file_size("foo.txt")?;
if size > 1024 {
return Err(Error::FileTooLarge { size });
}
Ok(())
}
}
mod crate_two {
#[derive(Debug)]
pub struct Error(pub std::io::Error);
impl From<std::io::Error> for Error {
fn from(error: std::io::Error) -> Self {
Self(error)
}
}
pub fn file_size(filename: &str) -> Result<u64, Error> {
let size = std::fs::File::open(filename)?
.metadata()?
.len();
Ok(size)
}
}
Converting the code to provide error traces can be done in a few simple steps:
- Replace all
Result<_, E>
withResult<_, Traced<E>>
- Use
ErrorExt::traced()
in all places where a new error is returned.
And that’s it! Any existing type conversions that were happening as a result
of the ?
operator will continue to work after switching over to tres
.
Here’s what the final result looks like:
mod crate_one {
use tres::{ErrorExt, Traced};
/* ... */
// Result uses `Traced`.
pub fn foo() -> Result<(), Traced<Error>> {
// `?` operator converts `Traced<crate_two::Error>` to `Traced<Error>`!
let size = crate_two::file_size("foo.txt")?;
if size > 1024 {
// `ErrorExt::traced()` converts `Error` to `Traced<Error>`.
return Err(Error::FileTooLarge { size }.traced());
}
Ok(())
}
}
mod crate_two {
use tres::Traced;
/* ... */
// Result uses `Traced`.
pub fn file_size(filename: &str) -> Result<u64, Traced<Error>> {
// `?` operator converts `std::io::Error` to `Traced<Error>`!
let size = std::fs::File::open(filename)?
.metadata()?
.len();
Ok(size)
}
}
And if we run that code…
fn main() {
let error = crate_one::foo().unwrap_err();
println!("{:?}", error);
}
We get the output below, complete with an error trace!
CrateTwo(Error(Os { code: 2, kind: NotFound, message: "No such file or directory" })):
[src/lib.rs:51:20, src/lib.rs:26:20]
Caveat: Remember to propagate!
The error trace inside a Traced
is appended to only when
propagated using the try (?
) operator. Because of this, it is important to
ensure that all results in your code are propagated using the try operator,
otherwise your error traces may end up missing certain locations.
This can be avoided by ensuring that all return values are surrounded by
Ok(..?)
:
fn gives_error() -> Result<(), Traced<&'static str>> {
Err(Traced::new("Oops!"))
}
fn foo() -> Result<(), Traced<&'static str>> {
// !! NO !!
gives_error()
}
fn bar() -> Result<(), Traced<&'static str>> {
// !! YES !!
Ok(gives_error()?)
}
TODO: implement a lint to detect missing Ok(..?)
.
Another option is to make use of try blocks. This makes it impossible to accidentally return a bare result without propagating it.
#![feature(try_blocks)]
fn foo() -> Result<(), Traced<&'static str>> {
try {
gives_error()?
}
}
#![feature(try_blocks)]
fn bar() -> Result<(), Traced<&'static str>> {
try {
// Does not compile without `?` operator!
gives_error()
}
}
How it works
The error tracing behavior of tres
is made possible by specializing the
Result
type’s behavior during try-propagation (?
operator). When the
Err
variant of the result supports tracing (as indicated by the presence
of the Trace
trait), each invocation of the ?
operator calls Trace::trace()
on the error
with the location of the ?
invocation.
For now, this behavior must be provided by a third party Result
type. The
tres-result
crate provides one; it is included in tres
as
Result
.
Eventually, this crate is meant to be used with the standard library
Result
type. There will likely be an RFC to add a Traced
trait to the
standard library and specialize the behavior or core::result::Result
in
the same way that tres_result::Result
is specialized.
Re-exports
Structs
A simple vector of Rust code locations.
Wraps a generic error value and keeps track of an error trace.
Enums
A drop-in replacement for core::result::Result
that supports return
tracing using the ?
operator.