error-chain
error-chain copied to clipboard
Using error-chain with platform code
Hi, I have a "platform" crate, which provides a trait that users of the platform will implement and pass into other platform code. This trait returns a Result<()>, so that errors from the user code can be propagated through the platform. My problem is that it seems like I need the platform code to have links to the user errors; however, the user code is defined in a different crate at a later time.
How should I attempt to solve this problem?
Well, error-chain doesn't support polymorphism so you can't do that. A variant with a Box<ErrorTrait> should do the trick.
I'm sorry, I'm pretty new to Rust & error-chain. Do you mean making a new variant of ErrorKind in the platform that contains the Box<ErrorTrait>? If I do that, will that also work for making the cause stack printing work?
It seems to me that Error::with_chain is the way to add/box an unknown error type into the platform's error chain--maybe I could use that and a platform-defined macro that generates the correct implementations for From?
Thank you for your help!
Yeah, that's it. Having a ErrorTrait: Error would allow you to add methods specific to you use case, but if you don't need it you can directly use Error.
with_chain would be more for things like "config error caused by file not found", so if you don't want to add an extra context layer, you shouldn't need it.
Could you give a bit more context, like a simple pseudo-code that uses your lib?
Absolutely: I'll write a simplified version of what I'm trying to do: In this pseudo-code, the platform defines its error types, a runtime (run_processor), and a trait for runnable things. Then, a plugin create defines the MyProcessor, which could fail, but needs to have its error be a cause for the platform error. Finally, the 3rd crate creates a processor & a runtime, and runs the processor via the runtime. The runtime needs to bubble up the errors whether they occur in the runtime or the plugin, but I'm not sure how to make that ergonomic for "processor" authors, and still support the richness of these error types.
//in the platform crate
pub mod errors {
error_chain! {
links { PlatformDependency(...); }
}
}
use errors::*:
pub trait Processor {
fn step(&mut self) -> Result<()>;
}
pub fn run_processor<P: Processor>(p: P) -> Result<()> {
// does all kinds of complex things in addition to calling step()
}
//in the plugin/extension crate
pub mod errors {
error_chain! {
links {
Platform(...);
OtherLib(...);
}
}
}
use errors::*:
struct MyProcessor { ... }
fn do_something_that_could_fail() -> Result<()> {
/// ...
}
impl Processor for MyProcessor {
fn step(&mut self) -> platform::errors::Result<()> {
//do stuff in here that can fail with this crate's error type
//of course, the following won't work
do_something_that_could_fail().chain_err(|| "reasons")?;
Ok(())
}
}
//in the application crate
use platform::*;
use myprocessor::*;
pub fn main() {
let r = run_processor(MyProcessor::new());
// finally, here we check r and print the cause trace if needed
}
If you don't need extra error processing, then a Box<Error> should do the trick.
@I'm sorry, I don't understand what you mean. The core of the issue is that the line do_something_that_could_fail().chain_err(|| "reasons")?; won't compile, since it's using ResultExt from myprocessor, which doesn't automatically coerce to platform::errors::Result.
Thank you so much for your continued help & suggestions!
I mean Box<Error> as an enum variant.
So I'd have something like this:
//in the platform crate
pub mod errors {
error_chain! {
errors {
PluginError(cause: Box<Error>);
}
links { PlatformDependency(...); }
}
}
In that case, if I wanted to make not finding something in a map an error, could I then do:
//in the plugin crate
fn step(&mut self) -> platform::errors::Result<()> {
let m = HashMap::new();
// populate hash map...
let value = m.get(some_key).ok_or(Error::from("couldn't find item"))?;
// do more stuff with value...
Ok(())
}
I don't understand whether I should explicitly need to lift the errors that take place in the plugin's error handling system into the platform's PluginError, and if there's any way to continue to make use of relatively unadorned ?/try! syntax. If you have time, could you please show me a bit of pseudocode?
Also, here's the specific error I get when trying to return a error from the plugin crate in the implementation of step() from the platform::Processor trait:
error[E0277]: the trait bound `platform::errors::Error: std::convert::From<errors::Error>` is not satisfied
--> src/main.rs:91:30
|
91 | let new_things = new_records.get(k).ok_or(Error::from("Failed to get record"))?;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::convert::From<errors::Error>` is not implemented for `platform::errors::Error`
|
= help: the following implementations were found:
<platform::errors::Error as std::convert::From<api::errors::Error>>
<platform::errors::Error as std::convert::From<std::io::Error>>
<platform::errors::Error as std::convert::From<std::sync::mpsc::SendError<api::Step>>>
<platform::errors::Error as std::convert::From<std::sync::mpsc::SendError<api::Op>>>
and 3 others
= note: required by `std::convert::From::from`
Since that Error::from call is creating a plugin crate error, it doesn't seem like ? is able to automatically convert it to a platform crate error.
Edit: if I explicitly use with_chain, then I can get the types to work; however, that's not ergonomic since every use of ? needs the additional with_chain call. For example, I can use
let e: Error = "local error type".into();
let new_events = new_records.get(k).ok_or(::platform_errors::Error::with_chain(e, "casted"))?;
and that compiles correctly. I'm just not sure how to reduce/eliminate the boilerplate, by having the ? trigger an automatic From conversion. Even if the conversion always wrapped it with a standard error message like "Error in plugin", that'd still be useful from a debugging perspective. What do you think?
Hey @Yamakaky, I've been continuing to work on figuring this out, to no avail. I have added a variant to foreign_links which is PluginError(Box<::std::error::Error + Send + 'static>), but that fails to compile due to the following error (I think that the ThreadingError reference is misleading and due to the macroexpansion's interaction with the compiler error message):
error[E0277]: the trait bound `std::error::Error + std::marker::Send: std::marker::Sized` is not satisfied
--> engine/src/lib.rs:29:5
|
29 | error_chain! {
| _____^ starting here...
30 | | errors {
31 | | ThreadingError
32 | | }
... |
41 | | }
42 | | }
| |_____^ ...ending here: the trait `std::marker::Sized` is not implemented for `std::error::Error + std::marker::Send`
|
= note: `std::error::Error + std::marker::Send` does not have a constant size known at compile-time
= note: required because of the requirements on the impl of `std::error::Error` for `std::boxed::Box<std::error::Error + std::marker::Send>`
= note: required by `std::error::Error::description`
= note: this error originates in a macro outside of the current crate
If, instead, I don't create a foreign link to a boxed stdlib Error trait, and I try to create a boxed error to be coerce to a platform error, it seems that there's no From implementation from boxed error trait objects to error_chain errors:
ultrasim_engine::errors::Error: std::convert::From<std::boxed::Box<std::error::Error>>` is not satisfied
--> first-analysis/src/main.rs:92:30
|
92 | let new_events = new_records.get(k).ok_or(e)?;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::convert::From<std::boxed::Box<std::error::Error>>` is not implemented for `ultrasim_engine::errors::Error`
|
= help: the following implementations were found:
<ultrasim_engine::errors::Error as std::convert::From<ultrasim_api::errors::Error>>
<ultrasim_engine::errors::Error as std::convert::From<std::io::Error>>
<ultrasim_engine::errors::Error as std::convert::From<std::sync::mpsc::SendError<ultrasim_api::Step>>>
<ultrasim_engine::errors::Error as std::convert::From<std::sync::mpsc::SendError<ultrasim_engine::DirectorOp>>>
and 3 others
= note: required by `std::convert::From::from`
Can you help me understand what I'm missing? Thank you for all your help--I realize that this is probably a simple mistake on my part, in the end.
I've actually figured out what may be a bug/issue here: it doesn't appear to be possible to make the foreign link enum variant you suggested. I tried manually implementing impl From<Box<::std::error:Error + Send>> for Error, and I get this error:
error[E0277]: the trait bound `std::error::Error + std::marker::Send: std::marker::Sized` is not satisfied
--> engine/src/lib.rs:46:13
|
46 | Error::with_chain(e, ErrorKind::PluginError)
| ^^^^^^^^^^^^^^^^^ the trait `std::marker::Sized` is not implemented for `std::error::Error + std::marker::Send`
|
= note: `std::error::Error + std::marker::Send` does not have a constant size known at compile-time
= note: required because of the requirements on the impl of `std::error::Error` for `std::boxed::Box<std::error::Error + std::marker::Send>`
= note: required by `errors::Error::with_chain`
error: aborting
I don't understand why the inside of the Box needs to be Sized here, since the Box should itself be Sized for chaining to work.
I finally found a solution to this: I had to create another variant of with_chain that accepts a boxed trait object where std::error::Error is a trait object rather than type parameter bound. By implementing this pub fn with_boxed_chain<K>(error: Box<::std::error::Error + Send>, kind: K) -> Self where K: Into<ErrorKind>, I can finally implement a From that works with a boxed Error trait object. Whew!
Would you be interested in this as a pull request once I've done a bit more poking around to confirm it does what I think?
(sorry for the delay) I'm sorry, I'm a bit lost. What is you error_chain declaration and code which uses it?
Let me explain the root cause, since my code has been in flux as I've experimented with dozens of potential avenues (and it's currently using derive-error-chain, which had the exact same issue).
When we look at with_chain's impl, it's parameter E is a generic type which is bounded by std::error::Error + Send. This is a problem for a few reasons:
Fromis only implement forBox<Error>andBox<Error + Send + Sync>, so it's not possible to use arbitraryResulttypes that return into an error-chain, since there's no implementation ofFromthat will result in the requiredError + Send, which is related to some rust compiler issues I found that prevent Rust from using theBox<Error + Send + Sync>conversion.- In other cases, the compiler sees the
Box<Error + Send>trait object being passed towith_chain, which gets matched to anError + Sendthatwith_chainis supposed to box. Unfortunately, that means the compiler wants the size of the contents of the boxed trait object we're trying to chain with (which isn't possible, since the trait object's unsized)!
I was able to solve (2) by introducing another method on the error-chain generated Error type, with the signature pub fn with_boxed_chain<K>(error: Box<::std::error::Error + Send>, kind: K) -> Self where K: Into<ErrorKind>. Unfortunately, that still doesn't work with chain_err, which would probably need a similar new variant to chain with boxed error trait objects.
Doing this, I am able to make my platform-boundary API expect a Result<(),Box<std::error::Error + Sync + Send>, which will get boxed correctly by ?, and then I can use my new with_boxed_chain to pull that into the error chain.
So, to summarize the changes I made:
- Added
Syncimpl for the generatedError - Added
with_boxed_chaintoError - The last thing I'm not sure about is how to add a
chain_boxed_errtoResults that have boxed errors
I'm still pretty new to Rust, so it's possible that I'm missing something that would make the existing implementation work, instead!
Could you show some code? Y think you would be good if you added a Platform(Box<Error>) as a error variant, but I could be wrong.
I've written a test case for error chain 0.10.0:
#[macro_use]
extern crate error_chain;
pub mod errors_platform {
error_chain! {
errors {
PlatformError(cause: Box<Error>)
}
}
}
pub mod errors_user {
error_chain! {
links {
Platform(::errors_platform::Error, ::errors_platform::ErrorKind);
}
}
//One way to make this compile is that run_generic needs to have `+ Sync` added to the inside
//of the box, and then uncomment the following line:
unsafe impl Sync for Error {}
}
pub trait Run {
fn run_generic(&mut self) -> ::std::result::Result<(), Box<::std::error::Error + Send>>;
fn run_generic_fixed(&mut self)
-> ::std::result::Result<(), Box<::std::error::Error + Send + Sync>>;
fn run_platform(&mut self) -> ::errors_platform::Result<()>;
}
fn run_something(r: &mut Run) -> ::errors_platform::Result<()> {
r.run_generic_fixed().map_err(|e| {
::errors_platform::Error::with_chain(e, ::errors_platform::ErrorKind::from("context"))
})
}
struct Demo;
fn fail() -> ::errors_user::Result<()> {
Err(::errors_user::Error::from("help"))
}
impl Run for Demo {
fn run_generic(&mut self) -> ::std::result::Result<(), Box<::std::error::Error + Send>> {
Ok(fail()?)
}
fn run_generic_fixed(&mut self)
-> ::std::result::Result<(), Box<::std::error::Error + Send + Sync>> {
Ok(fail()?)
}
fn run_platform(&mut self) -> ::errors_platform::Result<()> {
Ok(fail()?)
}
}
In this code, you'll see that 2 of the 3 variations of run_* fail to compile:
run_generic fails because the From implementation that knows how to put an Error in a box only exists for Error and Error + Send + Sync (see this RustDoc and Ctrl-F for Error to see that there's no Error + Send impl). This is problematic because this library makes all errors Error + Send.
run_generic_fixed feels hacky, since it requires forces me to unsafe impl Sync for my error type so that my local error gets boxed.
run_platform obviously doesn't work, since there's no conversion from errors_user to errors_platform.
Furthermore, it's impossible to implement run_something, which would consume any of the run_* trait methods. This won't work because with_chain at the core tries to re-box the error, which can't be done since we are trying to rebox a boxed trait object, whose inside isn't Sized. I had to introduce a new with_boxed_chain method on the error. Here's the original and additional with_chain impls, that I had to use to be able to implement run_something (note that the only change is to not box the error argument, and instead look for a boxed trait argument rather than the generic impl which would require the error to be Sized):
pub fn with_boxed_chain<K>(error: Box<::std::error::Error + Send>, kind: K) -> Self
where K: Into<#error_kind_name> {
#error_name(kind.into(), #error_chain_name::State::new::<#error_name>(error))
}
pub fn with_chain<E, K>(error: E, kind: K) -> Self
where E: ::std::error::Error + Send + 'static, K: Into<#error_kind_name> {
#error_name(kind.into(), #error_chain_name::State::new::<#error_name>(Box::new(error)))
}
Hey @Yamakaky, I was wondering if you had time to look at the writeup I made with test cases that demonstrate the issue I'm running into. Thank you!