The problem
Rust's built-in error handling is rather bare-bones and requires a whole lot of effort to preserve a proper stack trace, where other languages give you that for free.
In order to show the inner error along with your error, you need to wrap the inner error explicitly:
#[derive(Debug)]enumServiceError{Json(serde_json::Error),Http(reqwest::Error),}
You then need to implementDisplay
with case arms for each enum:
implfmt::DisplayforServiceError{fnfmt(&self,f:&mutfmt::Formatter)->fmt::Result{match*self{ServiceError::Json(..)=>write!(f,"Could not parse the JSON"),ServiceError::Http(..)=>write!(f,"The HTTP call failed"),}}}
And then implementFrom
to allow the inner error types to convert to your error:
implFrom<serde_json::Error>forServiceError{fnfrom(err:serde_json::Error)->ServiceError{ServiceError::Json(err)}}implFrom<reqwest::Error>forServiceError{fnfrom(err:reqwest::Error)->ServiceError{ServiceError::Http(err)}}
Basic error-stack support
Theerror-stack crate makes this a lot better. Its error is aReport
, which stores the chain of errors it's encountered. That means you can define an error like this:
#[derive(Debug)]structServiceError;implfmt::DisplayforServiceError{fnfmt(&self,f:&mutfmt::Formatter)->fmt::Result{f.write_str("There was an error calling the service.")}}implErrorforServiceError{}
Then convert the error withinto_report()
:
useerror_stack::{Result,IntoReport};fnparse_it(value:&str)->Result<(),ServiceError>{letparsed:serde_json::Value=serde_json::from_str(value).into_report().change_context(ServiceError)?;println!("We parsed: {}",parsed);Ok(())}
Theinto_report()
packages up an outside error into aResult
, andchange_context()
adds our own error type as another link in the error chain. You can add another link by callingchange_context()
again with a different error type. No more annoyingFrom
implemenations!
TheReport
prints out beautifully with all information:
Error: experiment error: could not run experiment├╴at examples/demo.rs:51:18├╴unable to set up experiments│├─▶ invalid experiment description│ ├╴at examples/demo.rs:21:10│ ╰╴experiment 2 could not be parsed│╰─▶ invalid digit found in string ├╴at examples/demo.rs:19:10 ├╴backtrace with 31 frames (1) ╰╴"3o" could not be parsed as experiment━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━backtrace no. 1 0: std::backtrace_rs::backtrace::libunwind::trace at /rustc/e972bc8083d5228536dfd42913c8778b6bb04c8e/library/std/src/../../backtrace/src/backtrace/libunwind.rs:93:5 1: std::backtrace_rs::backtrace::trace_unsynchronized at /rustc/e972bc8083d5228536dfd42913c8778b6bb04c8e/library/std/src/../../backtrace/src/backtrace/mod.rs:66:5 2: std::backtrace::Backtrace::create at /rustc/e972bc8083d5228536dfd42913c8778b6bb04c8e/library/std/src/backtrace.rs:332:13 3: core::ops::function::FnOnce::call_once at /rustc/e972bc8083d5228536dfd42913c8778b6bb04c8e/library/core/src/ops/function.rs:250:5 4: core::bool::<impl bool>::then at /rustc/e972bc8083d5228536dfd42913c8778b6bb04c8e/library/core/src/bool.rs:71:24 5: error_stack::report::Report<C>::from_frame at ./src/report.rs:288:25 6: error_stack::report::Report<C>::new at ./src/report.rs:274:9 7: error_stack::context::<impl core::convert::From<C> for error_stack::report::Report<C>>::from at ./src/context.rs:83:9 8: <core::result::Result<T,E> as error_stack::result::IntoReport>::into_report at ./src/result.rs:203:31 (For this example: additional frames have been removed)
You can attach other bits of arbitrary data to the report as well.
Tauri integration
Tauri will not let you directly return aerror_stack::Result
from a#[tauri::command]
. If you try, it will complain:
no method
blocking_kind
on type&Result<(), Report<ServiceError>>
Tauri lets you return anything that implementsSerialize
, but aReport
does not. To get around this we can use a wrapper:
#[derive(Debug,Serialize)]pubstructCommandError(String);impl<C>From<Report<C>>forCommandError{fnfrom(err:Report<C>)->CommandError{CommandError(format!("{:?}",err))}}implfmt::DisplayforCommandError{fnfmt(&self,f:&mutfmt::Formatter)->fmt::Result{f.write_str(&self.0)}}
That allows us to easily map ourReport
s to an error type that Tauri can send to the frontend:
#[tauri::command]asyncfncall_the_service()->core::result::Result<(),CommandError>{my_module::call_service().await?;Ok(())}
That will be visible in the Dev console for the frontend page.{:?}
is the debug format that includes all error information; if you want to obfuscate for production, you'll want to use{:#}
.
Top comments(0)
For further actions, you may consider blocking this person and/orreporting abuse