Bad rustc error message

This is just insulting

the trait bound `anyhow::Error: Serialize` is not satisfied
the following other types implement trait `Serialize`:
&'a T
&'a mut T
()
(T0, T1)
(T0, T1, T2)
(T0, T1, T2, T3)
(T0, T1, T2, T3, T4)
(T0, T1, T2, T3, T4, T5)
and 579 others

REALLY? Let me give you a car analogy: How about a roadsign: "Watch for animals next 579 miles"

required for `std::option::Option<anyhow::Error>` to implement `Serialize`rustc[Click for full compiler diagnostic](rust-analyzer-diagnostics-view:/diagnostic%20message%20%5B2%5D?2#file%3A%2F%2F%2FVolumes%2FAPFS%2Fgit%2Fptisms%2Fsrc%2Fprocess_lookup.rs)

process_lookup.rs(11, 9): required by a bound introduced by this call
mod.rs(1871, 12): required by a bound in `phone::_::_serde::ser::SerializeStruct::serialize_field`

Of all the unhelpful error messages produced by this compiler, this has to be near the top.
As far as I know, The Phone datatype isn't implicated here. The compiler can't even provide a trace to figure out the connection. "I know you meant to spell Mississippi. Should I correct your misspelling?"

Here's the enum causing this flammage

use anyhow::Error as anyhowError;
use serde::{Serialize};
#[derive(Debug, Serialize)]
#[serde(untagged)]
pub enum Error {
    InvalidInput {
        cause: Option<anyhowError>,
    },
}

I can't believe error handling is so inconsistent/difficult in this language that I have to rely on something like anyhow to straighten out this mess.

RA or your IDE may be hiding the useful part of the error message from you.

error[E0277]: the trait bound `anyhow::Error: Serialize` is not satisfied
    --> src/lib.rs:8:9
     |
8    |         cause: Option<anyhowError>,
     |         ^^^^^ the trait `Serialize` is not implemented for `anyhow::Error`
     |
     = help: the following other types implement trait `Serialize`:
[...]

Namely, anyhowError: Serialize is not true, so you can't derive Serialize.

You presumably don't want to replace anyhowError with any of the 587 possibilities in your crate and dependencies, so you can just ignore the list and get on with figuring out how to serialize something that's been type erased.

7 Likes

anyhow::Error is essentially a fancy Box<dyn Error + Send + Synd + 'static>. How do you expect it to be serializable? Well… perhaps it’s more realistic than de-serializable, since errors can be converted into a String via Display, but if that’s the intention, you’ll have to indicate/implement it.

Honestly though… I’m not even sure what the point of your post is. The error message is clear as night and day

the trait bound `anyhow::Error: Serialize` is not satisfied

end of story. The rest is just help and note extra information.

For traits with fewer implementors, listing them can be useful information for an error where your type didn’t have an implementation. Maybe you can spot some similar type in that list, which you’d rather use instead.

Sure the and 579 others kills that purpose a little bit. I don’t get the car sign anology though. It’s more like a digital “579 free parking spots” sign on a car park. You can choose any of those parking spots, if you’re interested, but mostly it’s just some friendly extra information.

As for how to use a Display implementation for serde … the most concise way I know of would be to use the serde_with crate, then it’s just

use anyhow::Error as anyhowError;
use serde::{Serialize};
use serde_with::{serde_as, DisplayFromStr};

#[serde_as]
#[derive(Debug, Serialize)]
#[serde(untagged)]
pub enum Error {
    InvalidInput {
        #[serde_as(as = "Option<DisplayFromStr>")]
        cause: Option<anyhowError>,
    },
}

(run online)

7 Likes

This tone doesn't help at all. Yes, sometimes error messages are subpar. You don't have to be personally offended by them. Producing good error messages is difficult.


As for the inconsistency and difficulty of error handling: I'm not sure how it is even relevant here. The compiler error has nothing to do with error handling itself.

You didn't explain what you think is "inconsistent" about error handling, anyway (Rust's error handling system is one of the most consistent out there), and the perceived difficulty may very well come from your expectations that it "should" be like in other languages. It's perfectly possible to provide a very smooth error handling experience if you write your own error types, for example (or use something like thiserror to actually use the type system instead of trying to shove everything into an anyhow-shaped hole).

4 Likes

I disagree with the assertion that error handling is easy. It's only easy when one has control over all error messages and their content in specific contexts. That's the problem that anyhow solves. I have one crate from Twilio implementing errors as generated by OpenAPI. The code generated is somewhat older than one would like. I have another self-produced library interfacing with a baroque xmlrpc server. Then I have a moka cache that also is quite useful.

Integrating the separate error message techniques (struct, enum) and a requirement for serialization has proven to be quite frustrating.

I'm sorry, but telling me that the error has 579 "something else" is useless beyond calling out for its facetious tautology.

I've seen recommendations both ways: anyhow vs. thiserror. anyhow was chosen first. I think I'm doing it wrong. Given this reply, it looks like anyhow isn't designed to be serialized. Use Display instead.

Agreed. I appreciate the analogy improvement!
As noted here, anyhow is not designed out-of-the-box for serialization. On to fmt::Display
As to the "very clear" part: Phone is implicated! Take a look above for the string below:

required by a bound in `phone::_::_serde::ser::SerializeS.....

I cannot figure out how Phone gets implicated. Phone is my Type, with some supporting impls like Display and From

Yes, I see that now, thanks!

As noted earlier, I will have to serialize according to app-specific requirements.

What part of error handling in Rust do you find inconsistent/difficult?

Can you give counter examples of better ways of doing error handling? Perhaps from other programming languages?

To be honest, I don't use any error handling library for handling errors in Rust. I prefer to be in full control on what's going on, even at the expense of some extra boilerplate code. Everytime that I introduce a new dependency, it's as simple as writing a new From<new_dependency::Error> for MyError implementation and I'm ready to work with it.

Having worked with exception-based languages for years, I find the composability of Rust's Result type the most elegant way of doing error handling.

2 Likes

Yes, the composability is nice. I'm certainly familiar with exception-based technology. While Result<> is a nice improvement, I'm finding that binding dissimilar implementations while retaining context-specific data can be challenging.
I also notice that such error handling is very call-frame oriented. For the instant project, I'm using procedural calls that communicate via tx/rx command message passing.

Ah thats your issue here? That’s very easy to explain! Here, let’s run

use anyhow::Error as anyhowError;
use serde::{Serialize};
#[derive(Debug, Serialize)]
#[serde(untagged)]
pub enum Error {
    InvalidInput {
        cause: Option<anyhowError>,
    },
}

through “expand macros” in the playground, focusing on the part that derive(Deserialize) generated:

#[doc(hidden)]
#[allow(non_upper_case_globals, unused_attributes, unused_qualifications)]
const _: () =
    {
        #[allow(unused_extern_crates, clippy :: useless_attribute)]
        extern crate serde as _serde;
        #[allow(unused_macros)]
        macro_rules! try {
            ($__expr : expr) =>
            {
                match $__expr
                {
                    _serde :: __private :: Ok(__val) => __val, _serde ::
                    __private :: Err(__err) =>
                    { return _serde :: __private :: Err(__err) ; }
                }
            }
        }
        #[automatically_derived]
        impl _serde::Serialize for Error {
            fn serialize<__S>(&self, __serializer: __S)
                -> _serde::__private::Result<__S::Ok, __S::Error> where
                __S: _serde::Serializer {
                match *self {
                    Error::InvalidInput { ref cause } => {
                        let mut __serde_state =
                            match _serde::Serializer::serialize_struct(__serializer,
                                    "Error", 0 + 1) {
                                _serde::__private::Ok(__val) => __val,
                                _serde::__private::Err(__err) => {
                                    return _serde::__private::Err(__err);
                                }
                            };
                        match _serde::ser::SerializeStruct::serialize_field(&mut __serde_state,
                                "cause", cause) {
                            _serde::__private::Ok(__val) => __val,
                            _serde::__private::Err(__err) => {
                                return _serde::__private::Err(__err);
                            }
                        };
                        _serde::ser::SerializeStruct::end(__serde_state)
                    }
                }
            }
        }
    };

As you can see, there is a extern crate serde as _serde; involved that allows the derived crate to (relatively) reliably refer to the root of the actual crate “serde”, even if some things were renamed, or another thing called serde is in scope. (I’m not sure off the top of my head, just how reliably this approach works… but anyways…)

This will thus, in your case, mean that the _serde from that extern crate line is scoped in a longer path – i.e. the path to your current module, and then the const (represented as _) presumably – which explains the meaning of phone::_::_serde::ser::SerializeStruct::serialize_field.

It’s ultimately the call to

_serde::ser::SerializeStruct::serialize_field(&mut __serde_state,
                                "cause", cause)

that causes the compilation error, as that function does require its last argument to be (a reference to) a value of a Serialize type.

The error message becomes better if the macro expansion is inserted manually:
Rust Playground

now featuring

error[E0277]: the trait bound `anyhow::Error: Serialize` is not satisfied
    --> src/lib.rs:32:42
     |
31   |                         match _serde::ser::SerializeStruct::serialize_field(&mut __serde_state,
     |                               --------------------------------------------- required by a bound introduced by this call
32   |                                 "cause", cause) {
     |                                          ^^^^^ the trait `Serialize` is not implemented for `anyhow::Error`
     |

fully explaining the connection. But with macro-expansions, it’s hard to blame the compile for being unable to point to any source code directly responsible for the error, and at least serde_derive is nice enough to set it up to point to at least the right field (as @quinedot already demonstrated).

Also, I’m wondering how you’re managing to actually make is show this phone::_ prefix. I’ve tried a few things and it didn’t do it for me, in the playground, for current stable or nightly compilers. What is your surrounding module structure like and what’s the compiler version?

Hah!

Give me a while to put something together for a reply. It could be tomorrow. Thanks so much!