In Rust, How do I go about ensuring convertibility of types to std::error:Error throught traits when the types implement from()? - rust

quite new to rust and trying to find myself around traits - I am working on a custom testing module and am trying to construct Result types where the Err is of a TestIssue enum that either implements an Error or Failure. I essentially want any Error type that implements std::error::Error to be convertible to a TestIssue::Error type, but also want to be able to handle cases like anyhow::Error where I can call into() to convert to an std::error::Error
#[derive(Debug)]
pub enum TestIssue<E: std::error::Error> {
Error(E),
Failure(String),
}
impl<E: std::error::Error + std::convert::From<anyhow::Error>> From<anyhow::Error> for TestIssue<E> {
fn from(error: anyhow::Error) -> TestIssue<E> {
TestIssue::Error(error.into())
}
}
impl<E: std::error::Error> From<E> for TestIssue<E> {
fn from(error: E) -> TestIssue<E> {
TestIssue::Error(error)
}
}
#[async_trait]
pub trait RunnableTest {
type Error: std::error::Error;
async fn run(&self) -> Result<(), TestIssue<Self::Error>>;
}
Strangely in some compiler errors I get, it says that the From implementations are conflicting even though anyhow::Error doesn't implement the std::error::Error trait from the docs.
conflicting implementations of trait `std::convert::From<anyhow::Error>` for type `TestIssue<anyhow::Error>`
This is confusing to me as I'd expect the From implementations to be unrelated to each other if the fact that anyhow::Error doesn't implement the std::error::Error trait still holds true...
impl<E: std::error::Error + std::convert::From<anyhow::Error>> From<anyhow::Error> for TestIssue<E> {
| --------------------------------------------------------------------------------------------------- first implementation here
...
18 | impl<E: std::error::Error> From<E> for TestIssue<E> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `TestIssue<anyhow::Error>`
Another issue I'm facing has to do with the RunnableTest::Error type - it appears if I have it impl std::error::Error it causes issues when attempting to return anyhow::Error in concrete implementations - I believe this has to do with the earlier issue of anyhow::Error only implementing into() but not the trait directly - When I try to change it to something like this:
type Error: Into<dyn std::error::Error + Sized>;
I get other issues like this:
31 | type Error: Into<dyn std::error::Error + Sized>;
| ----------------- ^^^^^ additional non-auto trait
Curious about how reasonable the current design is or if I'm missing something with respect to traits that could make this more simple. Given how new I am to the language, any other guidance or general comments would be much appreciated as well!

If you read the full error message, you'll note that it says:
note: upstream crates may add a new impl of trait std::error::Error for type anyhow::Error in future versions
This is a bit similar to the orphan rules. Right now the anyhow error doesn't implement the std::error::Error trait but it may choose to do so in the future and you have no control over that. This would then introduce subtle behavioral changes in your code.
Therefore, using generics and traits and trait implementations is a bit more restrictive right now than it might have to be. See discussion here:
How do I work around the "upstream crates may add a new impl of trait" error?
The way to work around this is to provide your trait implementations for concrete types, possibly using macros to reduce the amount of boilerplate, or to pick one interface (either the errors from std or the anyhow crate but not both) and stick with that.
Maybe work that out first and then come back with another question about the RunnableTest stuff? :)

Related

Why these errors are not automagically converted to my custom error even if they are implementing both std::error::Error trait?

Reproduction project (single main.rs file): https://github.com/frederikhors/iss-custom-err.
I'm trying to create a custom error for my app:
pub struct AppError {
message: String,
error: anyhow::Error, // In the future I would also avoid anyhow
}
I'm trying to use it in my code but as you can see I'm getting the below compiler errors, why?
Isn't my AppError implementing the trait std::error::Error correctly?
I would expect an auto conversion from hyper error to AppError being both error:Error traits, am I wrong?
error[E0277]: `?` couldn't convert the error to `AppError`
--> src\main.rs:20:44
|
20 | .body(Body::from(body))?;
| ^ the trait `From<hyper::http::Error>` is not implemented for `AppError`
|
= note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
= help: the following other types implement trait `FromResidual<R>`:
<Result<T, F> as FromResidual<Result<Infallible, E>>>
<Result<T, F> as FromResidual<Yeet<E>>>
= note: required because of the requirements on the impl of `FromResidual<Result<Infallible, hyper::http::Error>>` for `Result<(), AppError>`
error[E0277]: `?` couldn't convert the error to `AppError`
--> src\main.rs:24:19
|
24 | .await?;
| ^ the trait `From<hyper::Error>` is not implemented for `AppError`
|
= note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
= help: the following other types implement trait `FromResidual<R>`:
<Result<T, F> as FromResidual<Result<Infallible, E>>>
<Result<T, F> as FromResidual<Yeet<E>>>
= note: required because of the requirements on the impl of `FromResidual<Result<Infallible, hyper::Error>>` for `Result<(), AppError>`
For more information about this error, try `rustc --explain E0277`.
I would expect an auto conversion from hyper error to AppError being both error:Error traits, am I wrong?
Yes. Error is a usage trait, it's like IntoIterator: both Vec and HashMap implement it, but that doesn't mean you can convert an arbitrary Vec to a HashMap.
Let alone that Rust will do it for you: Rust generally favors intentionality, it avoids large multi-use concepts. So all error tells you is that you can display the type, and may be able to get its source. It says nothing about conversion, except for conversion to a Box<dyn Error> (because Error is object-safe).
So as the compiler error tells you, if you want Rust (and specifically ?) to perform the conversion, you need to implement the From trait.
Note: libraries like thiserror or snafu provide tooling to more easily create bespoke error types, and conversions from existing error types.
On discord a kind user helped me understand.
What I need is a generic impl:
impl<T: error::Error + Send + Sync + 'static> From<T> for AppError {
fn from(e: T) -> Self {
Self { message: e.to_string(), error: anyhow::Error::new(e) }
}
}
That's it!

How can I implement `From<some trait's associated type>?`

I'm writing a new crate, and I want it to be useable with any implementation of a trait (defined in another crate). The trait looks something like this:
pub trait Trait {
type Error;
...
}
I have my own Error type, but sometimes I just want to forward the underlying error unmodified. My instinct is to define a type like this:
pub enum Error<T: Trait> {
TraitError(T::Error),
...
}
This is similar to the pattern encouraged by thiserror, and appears to be idiomatic. It works fine, but I also want to use ? in my implementation, so I need to implement From:
impl<T: Trait> From<T::Error> for Error<T> {
fn from(e: T::Error) -> Self { Self::TraitError(e) }
}
That fails, because it conflicts with impl<T> core::convert::From<T> for T. I think I understand why — some other implementor of Trait could set type Error = my_crate::Error such that both impls would apply — but how else can I achieve similar semantics?
I've looked at a few other crates, and they seem to handle this by making their Error (or equivalent) generic over the error type itself, rather than the trait implementation. That works, of course, but:
until we have inherent associated types, it's much more verbose. My T actually implements multiple traits, each with their own Error types, so I'd now have to return types like Result<..., Error<<T as TraitA>::Error, <T as TraitB>::Error>> etc;
it's arguably less expressive (because the relationship to Trait is lost).
Is making my Error generic over the individual types the best (most idiomatic) option today?
Instead of implementing From for your Error enum, consider instead using Result::map_err in combination with ? to specify which variant to return.
This works even for enums generic over a type using associated types, as such:
trait TraitA {
type Error;
fn do_stuff(&self) -> Result<(), Self::Error>;
}
trait TraitB {
type Error;
fn do_other_stuff(&self) -> Result<(), Self::Error>;
}
enum Error<T: TraitA + TraitB> {
DoStuff(<T as TraitA>::Error),
DoOtherStuff(<T as TraitB>::Error),
}
fn my_function<T: TraitA + TraitB>(t: T) -> Result<(), Error<T>> {
t.do_stuff().map_err(Error::DoStuff)?;
t.do_other_stuff().map_err(Error::DoOtherStuff)?;
Ok(())
}
On the playground
Here the important bits are that Error has no From implementations (aside from blanket ones), and that the variant is specified using map_err. This works as Error::DoStuff can be interpreted as a fn(<T as TraitA>::Error) -> Error when passed to map_err. Same happens with Error::DoOtherStuff.
This approach is scaleable with however many variants Error has and whether or not they are the same type. It might also be clearer to someone reading the function, as they can figure out that a certain error comes from a certain place without needing to check From implementations and where the type being converted from appears in the function.

How do I work around the "upstream crates may add a new impl of trait" error?

I have created a trait for transforming from some values to a type I need. That conversion is already covered by From/Into for many types, but not everything I want. I thought I could exploit this, but quickly got an error "upstream crates may add a new impl of trait".
(stripped-down example in the playground)
pub trait Cookable {
fn cook(self) -> (String, Vec<i8>);
}
impl<T: Into<Vec<i8>>> Cookable for T {
fn cook(self) -> (String, Vec<i8>) {
(String::from("simple"), self.into())
}
}
impl Cookable for &str {
fn cook(self) -> (String, Vec<i8>) {
(String::from("smelly"), vec![self.len()])
}
}
That triggers the following error:
error[E0119]: conflicting implementations of trait `Cookable` for type `&str`:
--> src/lib.rs:11:1
|
5 | impl<T: Into<Vec<i8>>> Cookable for T {
| ------------------------------------- first implementation here
...
11 | impl Cookable for &str {
| ^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `&str`
|
= note: upstream crates may add a new impl of trait `std::convert::From<&str>` for type `std::vec::Vec<i8>` in future versions
I am worried that the only way to work around this error is to specify individual trait implementations for every one of the types that already has an Into.
It's not a problem you can "work around". It's a limitation imposed by the compiler to prevent future changes to your dependencies from subtly changing the behavior of your code.
For now, you avoid the error by implementing for concrete types instead of using generics and traits. A macro is one way to reduce the amount of keyboard entry you have to do.
In the future, some form of specialization might also be useful to solve this. However, this sits squarely in the middle of the reason that specialization isn't stable. It's possible to use this type of specialization to create unsound Rust using only safe code. A reduced form of specialization is being worked on, but it deliberately eschews the ability to specialize based on a trait and only works for concrete types.
See also:
Resolving trait implementation conflicts
Conflicting implementations of trait in Rust
How can I implement From for both concrete Error types and Box<Error> in Rust?
How is there a conflicting implementation of `From` when using a generic type?
I implemented a trait for another trait but cannot call methods from both traits
Based on the answers to the questions listed by Shepmaster, I have come up with the following workaround which appears to accomplish my mission. It is an implementation of the "use a macro to compactly do all the boilerplate of implementing the trait for a bunch of structs":
extern crate jni;
use jni::objects::{JObject, JValue};
use jni::JNIEnv;
pub trait ConvertRustToJValue<'a> {
fn into_jvalue(self, je: &JNIEnv<'a>) -> JValue<'a>;
}
macro_rules! impl_convert_rust_to_jvalue {
( $($t:ty),* ) => {
$( impl<'a> ConvertRustToJValue<'a> for $t
{
fn into_jvalue(self, _je:&JNIEnv) -> JValue<'a>
{
self.into()
}
}) *
}
}
impl_convert_rust_to_jvalue! { i8, i16, i32, i64 }

What is the mechanism that allows failure::Error to represent all errors?

For example:
extern crate failure;
use std::fs::File;
fn f() -> std::result::Result<(), failure::Error>
let _ = File::open("test")?;
"123".parse::<u32>()?;
Ok(())
}
What is the technique that allows failure::Error to be able to represent std::io::Error, the parse error, and any other custom error types? What is the minimal implementation to recreate this ability?
There are two mechanisms at play here.
The first mechanism is the question mark operator, which returns Err(From::from(e)) when it encounters an Err(e). If the function return type is Result<T, E>, this allows us to return any error type F that E implements From<F> for.
From the documentation of the failure::Error type, we can see that there is a generic From implementation for all types implementing the failure::Fail trait, and there is a generic implementation of Fail for all types implementing std::error::Error (as long as they are also Send + Sync + 'static). In combination, this allows us to return any type implementing either the failure::Fail trait or the std::error::Error trait. All error types in the standard library implement the Error trait, including std::io::Error and std::num::ParseIntError.
This already explains why the code compiles, but it does not explain how the conversions work internally. This is explained by the second mechanism at play – trait objects. The (slightly redacted) definition of the Error type in the failure crate is this:
struct Error {
imp: ErrorImpl,
}
struct ErrorImpl {
inner: Box<Inner<dyn Fail>>,
}
struct Inner<F: ?Sized + Fail> {
backtrace: Backtrace,
failure: F,
}
The Inner type stores the error as a trait object that uses dynamic dispatch for method calls.
The failure::Error::from_boxed_compat constructor is used to convert any error to a failure::Error.
pub fn from_boxed_compat(err: Box<dyn StdError + Sync + Send + 'static>) -> Error
This funciton takes any struct that impl Error as input and constructs a failure::Error
https://docs.rs/failure/0.1.5/failure/struct.Error.html#impl
failure::Error contains a heap stored trait object inside it that can store a struct that implements the Error object.
struct ErrorImpl {
inner: Box<Inner<Fail>>,
}
Additionally, it looks like the Fail trait is implemented for many errors. The ? operator will add an into method that will convert an Error to a failure::Error

Traits with stricter associated type bounds than supertrait

I have a simple trait with an associated type with no bounds.
trait Board {
type Move;
fn moves(&self) -> Vec<Self::Move>;
}
I also want to use this trait as a supertrait. In particular, I want my new subtrait to have stricter bounds on the associated type. Something like this:
trait TextBoard: Board {
type Move: fmt::Debug; // Trying to tighten bounds on associated type
fn printMoves(&self) {
println!("{:?}", self.moves());
}
}
The example is highly simplified, but seems to show the problem: The compiler thinks I'm trying to create a new associated type, but I just want the subtrait to require tighter bounds. Is there any way to achieve this?
Here's what you asked for:
trait TextBoard: Board
where
Self::Move: Debug,
{
// ...
}
All the bounds on a trait have to be in the "headline"; you can't impose additional restrictions once you start writing the body of the trait. This bound will prevent you from writing impl TextBoard for Foo when <Foo as Board>::Move does not implement Debug (playground).
Maybe that is what you want, but do you really need to prevent implementing TextBoard for other types? For some type there could be another way to write print_moves that makes more sense, and the Debug requirement is just noise. In that case you probably want to skip the where clause and move the body of print_moves to a blanket impl:
trait TextBoard {
fn print_moves(&self);
}
impl<B: Board> TextBoard for B
where
B::Move: Debug, // or <Self as Board>::Move: Debug
{
fn print_moves(&self) {
println!("{:?}", self.moves());
}
}
With this version, you still don't need to write an impl for types where Self::Move: Debug, but you're not prevented from writing an impl for other types where that doesn't hold. It's more of an extension than a refinement.
On the other hand, you should pretty much always implement Debug for every type, so is it really useful to have that trait? Maybe what you want is just an optional method on Board that's implemented when Move: Debug:
trait Board {
type Move;
fn moves(&self) -> Vec<Self::Move>;
fn print_moves(&self)
where
Self::Move: Debug,
{
println!("{:?}", self.moves());
}
}
This is like the original version, but doesn't require the addition of a new TextBoard trait, so it will probably cut down on the number of explicit bounds you have to write. Many of the standard library traits such as Iterator have optional methods defined with bounds like this. The downside, besides the requirement that Move must be Debug, is that it clutters the Board trait with printing code, which you might not consider really part of what it means to be a Board.

Resources