My Errors are fighting with each other

I have been using anyhow to return ´Result´s from some methods that use ssh2. Like so:

use anyhow::{Context, Result};
use std::error::Error;
...
    fn read_byte(&mut self) -> Result<u8, anyhow::Error> {
        let mut buf = [0u8];
        match self.read_exact(&mut buf) {
            Ok(_) => Ok(buf[0]),
            Err(e) => match e.kind() {
                ErrorKind::TimedOut => {
                    error!("SSH got Timeout: {e}");
                    Ok(ETX)
                }
                _ => {
                    error!("SSH got error: {e}");
                    Err(e.into())
                }
            },
        }
    }

I'd also like to look inside some of those errors returned by ssh2 like so:

use ssh2::{Channel, Session};
...
        let channel = match session.channel_session() {
            Ok(channel) => channel,
            Err(e) => {
                let error_source = e.source();
                let error_code = e.code();
                error!(
                    "Creating channel failed with: {} and {:?}",
                    error_code, error_source
                );
                // Other stuff ...
            }
        };

This all works but I thought it would be nice to write Result<u8, Error> instead of Result<u8, anyhow::Error> all over the place. So I try add Error to the anyhow use statement:

use anyhow::{Context, Error, Result};

But now the compiler complains that Error is defined multiple times because I'm using std::error::Error. I can't remove that as I then fails compilation with:

197 |                 let error_source = e.source();
    |                                      ^^^^^^ method not found in `Error`

Is there a neat way to stop these errors fighting with each other? How do I fix that compile error without going back to writing anyhoweverywhere?

Use an import alias:

1 Like

std::error::Error is a trait, while anyhow::Error is a struct. you can import the std::error::Error renamed or anonymously but still able to call the methods of the trait:

use std::error::Error as _;
5 Likes

Or just use anyhow::Result which has a default for the error type, and write Result<T> for Result<T, anyhow::Error>, and Result<T, E> for any other error type.

4 Likes

I usually do

use anyhow::Context;
...
fn read_byte(&mut self) -> anyhow::Result<u8>

Avoids any shadowing of Result or Error.

I might start doing:

use anyhow::Context as _;

too just to be on the safe side.

1 Like

Thanks for the suggestions. I have some concerns:

  1. This is all very complex. How is a poor boy supposed to gather all that from the docs. I did try...

  2. I'm a bit loath to start aliasing things. That seems like adding a layer of obfuscation to what is already confusing.

  3. I hardly understand what is going on. For example reading use std::error::Error as _; suggests that the name Error is thrown away. How can it be useful after that?

Anyway, after futzing around taking hints from your suggestions I have ended up with:

use anyhow::{Context, Result};
use std::error::Error;

Then I can define trait methods on and ssh2 channel as:

fn read_byte(&mut self) -> Result<u8>;

Which return ssh2 channel errors with:

                    Err(e.into())

and still get into some details of ssh2 errors with:

        let channel = match session.channel_session() {
            Ok(channel) => channel,
            Err(e) => {
                let error_source = e.source();
                let error_code = e.code();
                error!(
                    "Creating channel failed with: {} and {:?}",
                    error_code, error_source
                );
                return Err(e.into());
            }

Which seems OK. The word anyhow is no longer all lover the code.

Thanks all.

It merely means that the trait's methods can be used but the name of the trait itself can't
be mentioned.

TBH I find it the least complex part of the language. Imports are boring. You can get a feeling for what to do by writing a moderate amount of code in the language.

1 Like

That seems like some sneaky magic that hides what is going on.

Imports are boring. After one realises they don't actually import any code. All that code one wants to use is available without them. They only allow using shorter names to refer to it optionally with a different name.

Imports are boring. After one has figured out how to get at code in one file in a project from another file in the project. That was. struggle for me and many others it seems.

Anyway, it's not the imports causing. me to scratch my head. It's the various Results and Errors. Is what I'm looking at a trait or a struct? Where did it come from? How do I convert one to another? How do I get at any useful info inside?

All in all a mess of structs, traits and generics that is an abstraction too far for my C brain along with none to clear documentation.

it's true that std::error::Error is a trait while anyhow::Error is a struct (that is basically a trait object, on the inside).

So it's hard to see from the import alone what anything is. I'm heavily reliant on rust analyzer and the documentation to make sense of things like this.

Don't feel bad being surprised by it, we get used to it but there is room for confusion, even if there is technically a lack of ambiguity.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.